当前位置: 首页 > news >正文

Linux系统编程(二):标准 I/O 库(下)

参考引用

  • UNIX 环境高级编程 (第3版)
  • 嵌入式Linux C应用编程-正点原子

1. 标准 I/O 库简介

  • 标准 I/O 库是指:标准 C 库中用于文件 I/O 操作(如:读、写文件等)相关的一系列库函数的集合
    • 标准 I/O 库函数相关的函数定义都在头文件 <stdio.h> 中
    • 标准 I/O 库函数构建于文件 I/O(open()、read()、write()、lseek()、close()等)这些系统调用之上,如:标准 I/O 库函数 fopen() 就利用系统调用 open() 来执行打开文件的操作

    为什么需要标准 I/O 库?直接使用文件 I/O 系统调用不是更好吗?

    • 设计库函数是为了提供比底层系统调用更为方便、好用的调用接口,虽然标准 I/O 构建于文件 I/O 之上,但标准 I/O 却有它自己的优势

  • 标准 I/O 和文件 I/O 的区别如下
    • 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux 系统调用
    • 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的
    • 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性
      • 通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,如:系统调用的定义、功能、参数列表、返回值等往往都不同
      • 而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性
    • 性能、效率:标准 I/O 库在用户空间维护了自己的 stdio 缓冲区,所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O

2. FILE 指针

  • 所有文件 I/O 函数(open()、read()、write()、lseek()等)都是围绕文件描述符进行的,当调用 open()函数打开一个文件时,即返回一个文件描述符 fd,然后该文件描述符就用于后续的 I/O 操作
  • 而对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *),使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作
  • FILE 指针的作用相当于文件描述符,FILE 指针用于标准 I/O 库函数中,而文件描述符则用于文件 I/O 系统调用中
  • FILE 是一个结构体数据类型,它包含了标准 I/O 库函数为管理文件所需要的所有信息,包括用于实际 I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等
    • FILE 数据结构定义在标准 I/O 库函数头文件 stdio.h 中

3. 标准输入、标准输出和标准错误

  • 用户通过标准输入设备与系统进行交互,进程将从标准输入 (stdin) 文件中得到输入数据,将正常输出数据(如:printf 打印输出)输出到标准输出(stdout) 文件,而将错误信息(如:函数调用报错)输出到标准错误 (stderr) 文件
    • 标准输出文件和标准错误文件都对应终端的显示器,而标准输入文件则对应于键盘
  • 每个进程启动之后都会默认打开标准输入、标准输出以及标准错误,得到三个文件描述符,即 0(标准输入)、1(标准输出)、2(标准错误),在应用编程中可以使用宏 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 分别代表 0、1、2,这些宏定义在 unistd.h 头文件中
    #define STDIN_FILENO 0 /* Standard input. */
    #define STDOUT_FILENO1 /* Standard output. */
    #define STDERR_FILENO2 /* Standard error output. */
    
  • 0、1、2 是文件描述符,只能用于文件 I/O,标准 I/O 中,需围绕 FILE 类型指针进行,在 stdio.h 头文件定义
    // struct _IO_FILE 结构体就是 FILE 结构体,使用 typedef 进行了重命名
    extern struct _IO_FILE *stdin; /* Standard input stream. */
    extern struct _IO_FILE *stdout; /* Standard output stream. */
    extern struct _IO_FILE *stderr; /* Standard error output stream. *//* C89/C99 say they're macros. */
    #define stdin stdin
    #define stdout stdout
    #define stderr stderr
    

4. 打开文件 fopen()、关闭文件 fclose()

  • 标准 I/O 中,将使用库函数 fopen() 打开或创建文件
    #include <stdio.h>// path:参数 path 指向文件路径,可以是绝对路径或相对路径
    // mode:参数 mode 指定了对该文件的读写权限,是一个字符串
    // 返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *)
    FILE *fopen(const char *path, const char *mode);
    
  • 参数 mode 字符串类型,可取值如下
    在这里插入图片描述

在这里插入图片描述

  • 虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值
    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)
    
  • 调用 fclose() 库函数可以关闭一个由 fopen() 打开的文件
    #include <stdio.h>// 参数 stream 为 FILE 类型指针,调用成功返回 0;失败将返回 EOF(也就是-1)
    int fclose(FILE *stream);
    

5. 读文件和写文件

  • 可以使用 fread() 和 fwrite() 库函数对文件进行读、写操作
    #include <stdio.h>/*  ptr:fread() 将读取到的数据存放在参数 ptr 指向的缓冲区中size:fread() 从文件读取 nmemb 个数据项,每一个数据项大小为 size 个字节,所以总共读取 nmemb * size 个字节数据nmemb:参数 nmemb 指定了读取数据项的个数stream:FILE 指针
    */
    size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
    /*  ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中size:参数 size 指定了每个数据项的字节大小,与 fread() 函数的 size 参数意义相同nmemb:参数 nmemb 指定了写入的数据项个数,与 fread() 函数的 nmemb 参数意义相同stream:FILE 指针
    */
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    

5.1 使用 fwrite() 将数据写入到文件中

#include <stdio.h>
#include <stdlib.h>int main(void) {char buff[] = "Hello World!\n";FILE *fp = NULL;if ((fp = fopen("./test_file", "w")) == NULL) {perror("fopen error");exit(-1);}   printf("open success\n");if (fwrite(buff, 1, sizeof(buff), fp) < sizeof(buff)) {printf("fwrite error\n");fclose(fp);exit(-1);}   printf("write success\n");fclose(fp);exit(0);
}
$ gcc fw.c -o fw
$ ./fw 
open success
write success$ cat test_file 
Hello World!

5.2 使用 fread() 从文件中读取数据

#include <stdio.h>
#include <stdlib.h>int main(void) {char buf[50] = {0};FILE *fp = NULL;int size;/* 只读方式打开文件 */if ((fp = fopen("./test_file", "r")) == NULL) {perror("fopen error");exit(-1);}   printf("文件打开成功!\n");/* 读取 12 * 1=12 个字节的数据 */if ((size = fread(buf, 1, 12, fp)) < 12) {if (ferror(fp)) {  //使用 ferror 判断是否是发生错误printf("fread error\n");fclose(fp);exit(-1);}   /* 如果未发生错误则意味着已经到达了文件末尾 */}   printf("成功读取%d 个字节数据: %s\n", size, buf);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc fr.c -o fr
$ ./fr
文件打开成功!
成功读取12 个字节数据: Hello World!$ cat test_file 
Hello World!

6. fseek 定位

  • 库函数 fseek() 的作用类似系统调用 lseek(),用于设置文件读写位置偏移量
    • 调用库函数 fread()、fwrite() 读写文件时,文件的读写位置偏移量会自动递增,使用 fseek() 可手动设置文件当前的读写位置偏移量
    #include <stdio.h>// stream:FILE 指针
    // offset:与 lseek() 函数的 offset 参数意义相同
    // whence:与 lseek() 函数的 whence 参数意义相同
    int fseek(FILE *stream, long offset, int whence);
    

6.1 使用 fseek() 调整文件读写位置

#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char rd_buf[100] = {0};char wr_buf[] = "www.baidu.com\n";int ret;/* 打开文件 */if ((fp = fopen("./test_file", "w+")) == NULL) {perror("fopen error");exit(-1);}   printf("open seccess\n");/* 写文件 */if (fwrite(wr_buf, 1, sizeof(wr_buf), fp) < sizeof(wr_buf)) {printf("fwrite error\n");fclose(fp);exit(-1);}   printf("write success\n");/* 将读写位置移动到文件头部 */if (fseek(fp, 0, SEEK_SET) < 0) {perror("fseek error\n");fclose(fp);exit(-1);}   /* 读文件 */if ((ret = fread(rd_buf, 1, sizeof(wr_buf), fp)) < sizeof(wr_buf)) {printf("fread error\n");fclose(fp);exit(-1);}   printf("成功读取 %d 个字节数据: %s\n", ret, rd_buf);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc fseek.c -o fseek
$ ./fseek 
open seccess
write success
成功读取 14 个字节数据: www.baidu.com

7. ftell() 函数

  • 库函数 ftell() 可用于获取文件当前的读写位置偏移量
    #include <stdio.h>// 参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回 -1
    long ftell(FILE *stream);
    

7.1 使用 fseek() 和 ftell() 获取文件大小

#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;int ret;/* 打开文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("open success\n");/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0>) {perror("fseek error");fclose(fp);exit(-1);}/* 获取当前位置偏移量,也就得到了 testApp.c 整个文件的大小 */if ((ret = ftell(fp)) < 0) {perror("ftell error");fclose(fp);exit(-1);}   printf("file size: %d\n", ret);/* 关闭文件 */fclose(fp);exit(0);
}
$ gcc ftell.c -o ftell
$ ./ftell 
open success
file size: 13

8. 检查或复位状态

8.1 feof() 函数

  • 库函数 feof() 用于测试参数 stream 所指文件的 end-of-file 标志
    • 如果 end-of-file 标志被设置了,则调用 feof() 函数将返回一个非零值
    • 如果 end-of-file 标志没有被设置,则返回 0
    #include <stdio.h>int feof(FILE *stream);
    
    // 当文件的读写位置移动到了文件末尾时,end-of-file 标志将会被设置
    if (feof(file)) {/* 到达文件末尾 */
    }
    else {/* 未到达文件末尾 */
    }
    

8.2 ferror() 函数

  • 库函数 ferror() 用于测试参数 stream 所指文件的错误标志
    • 如果错误标志被设置了,则调用 ferror() 函数将返回一个非零值
    • 如果错误标志没有被设置,则返回 0
    #include <stdio.h>int ferror(FILE *stream);
    
    // 当对文件的 I/O 操作发生错误时,错误标志将会被设置
    if (ferror(file)) {/* 发生错误 */
    }
    else {/* 未发生错误 */
    }
    

8.3 clearerr() 函数

  • 库函数 clearerr() 用于清除 end-of-file 标志和错误标志
    • 当调用 feof() 或 ferror() 校验这些标志后,通常需要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr() 函数清除标志
    • 对于 end-of-file 标志,除使用 clearerr() 显式清除外,当调用 fseek() 成功时也会清除文件的 end-of-file 标志
    #include <stdio.h>void clearerr(FILE *stream);
    
  • 示例
#include <stdio.h>
#include <stdlib.h>int main(void) {FILE *fp = NULL;char buf[20] = {0};/* 打开文件 */if ((fp = fopen("./testApp.c", "r")) == NULL) {perror("fopen error");exit(-1);}printf("文件打开成功!\n");/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) < 0) {perror("fseek error");fclose(fp);exit(-1);}/* 读文件 */if (fread(buf, 1, 10, fp) < 10) {if (feof(fp))printf("end-of-file 标志被设置, 已到文件末尾!\n");clearerr(fp);  // 清除标志}/* 关闭文件 */fclose(fp);exit(0);
}

9. 格式化 I/O

9.1 格式化输出

  • C 库函数提供了 5 个格式化输出函数,包括:printf()、fprintf()、dprintf()、sprintf()、snprintf()
    #include <stdio.h>int printf(const char *format, ...);int fprintf(FILE *stream, const char *format, ...);
    int dprintf(int fd, const char *format, ...);int sprintf(char *buf, const char *format, ...);
    int snprintf(char *buf, size_t size, const char *format, ...);
    
  • 这 5 个函数都是可变参函数,它们都有一个共同的参数 format,这是一个字符串,称为格式控制字符串,用于指定后续的参数如何进行格式转换,所以才把这些函数称为格式化输出
    • printf() 函数用于将格式化数据写入到标准输出
    printf("Hello World!\n");
    printf("%d\n", 5);
    
    • dprintf() 和 fprintf() 函数用于将格式化数据写入到指定的文件中,两者不同之处在于,fprintf() 使用 FILE 指针指定对应的文件,而 dprintf()则使用文件描述符 fd 指定对应的文件
    fprintf(stderr, "Hello World!\n");
    fprintf(stderr, "%d\n", 5);dprintf(STDERR_FILENO, "Hello World!\n");
    dprintf(STDERR_FILENO, "%d\n", 5);
    
    • sprintf()、snprintf() 函数可将格式化的数据存储在用户指定的缓冲区 buf 中
    char buf[100];
    sprintf(buf, "Hello World!\n");// 一般会使用这个函数进行格式化转换,并将转换后的字符串存放在缓冲区中
    // 如:将数字 100 转换为字符串 "100",将转换后得到的字符串存放在 buf 中
    // sprintf() 函数会在字符串尾端自动加上一个字符串终止字符 '\0'
    char buf[20] = {0};
    sprintf(buf, "%d", 100);
    

    sprintf() 函数可能会造成由参数 buf 指定的缓冲区溢出,因为缓冲区溢出会造成程序不稳定甚至安全隐患,为解决这个问题,引入 snprintf() 函数,在该函数中,使用参数 size 显式的指定缓冲区的大小

    • 如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃
    • 如果缓冲区空间足够大,snprintf() 函数就会返回写入到缓冲区的字符数

9.2 格式控制字符串 format:输出

  • 格式控制字符串由两部分组成:普通字符(非 % 字符)和转换说明
    • 普通字符会进行原样输出,每个转换说明都会对应后续的一个参数,通常有几个转换说明就需要提供几个参数(除固定参数之外的参数),使之一一对应,用于控制对应的参数如何进行转换
    printf("转换说明 1 转换说明 2 转换说明 3", arg1, arg2, arg3);
    
  • 每个转换说明都是以 % 字符开头,其格式如下所示(使用 [] 括起来的部分是可选的)
    /*flags:标志,用于规定输出样式,可包含 0 个或多个标志width:输出最小宽度,表示转换后输出字符串的最小宽度precision:精度,前面有一个点号 "."length:长度修饰符type:转换类型,指定待转换数据的类型
    */
    %[flags][width][.precision][length]type
    
9.2.1 type 类型
  • type 用于指定输出数据的类型,type 字段使用一个字符(字母字符)来表示
    在这里插入图片描述

在这里插入图片描述

9.2.2 flags 样式标志
  • flags 规定输出样式,% 后面可以跟 0 个或多个以下标志
    在这里插入图片描述

在这里插入图片描述

9.2.3 width 输出宽度
  • 最小的输出宽度,用十进制数来表示输出的最小位数
    • 若实际的输出位数大于指定的输出的最小位数,则以实际的位数进行输出
    • 若实际的位数小于指定输出的最小位数,则可按照指定的 flags 标志补 0 或补空格
      在这里插入图片描述
9.2.4 precision 精度
  • 精度字段以点号 “.” 开头,后跟一个十进制正数,可取值如下
    在这里插入图片描述

在这里插入图片描述

9.2.5 length 长度修饰符
  • 长度修饰符指明待转换数据的长度,因为 type 字段指定的的类型只有 int、unsigned int 以及 double 等几种数据类型,但是 C 语言内置的数据类型不止这几种,如:16bit 的 short、unsigned short,8bit 的 char、unsigned char,64bit 的 long long 等,为了能够区别不同长度的数据类型,于是长度修饰符(length)应运而生
  • length 长度修饰符也是使用字符(字母字符)来表示,结合 type 字段以确定不同长度的数据类型
    printf("%hd\n", 12345);   // 将数据以 short int 类型进行转换
    printf("%ld\n", 12345);   // 将数据以 long int 类型进行转换
    printf("%lld\n", 12345);  // 将数据以 long long int 类型进行转换
    

在这里插入图片描述

9.3 格式化输入

  • C 库函数提供了 3 个格式化输入函数,包括:scanf()、fscanf()、sscanf()
    #include <stdio.h>int scanf(const char *format, ...);
    int fscanf(FILE *stream, const char *format, ...);
    int sscanf(const char *str, const char *format, ...);
    
    • 这 3 个格式化输入函数也是可变参函数,它们都有一个共同的参数 format,同样也称为格式控制字符串,用于指定输入数据如何进行格式转换
    • scanf() 函数可将用户输入(标准输入)的数据进行格式化转换
      • 当程序中调用 scanf() 的时候,终端会被阻塞,等待用户输入数据,此时可以通过键盘输入一些字符,如:数字、字母或者其它字符,输入完成按回车即可
    int a, b, c;
    scanf("%d %d %d", &a, &b, &c);
    
    • fscanf() 函数从 FILE 指针指定文件中读取数据,并将数据进行格式化转换
      • 标准输入文件的数据就是用户输入的数据,如:通过键盘输入的数据
    int a, b, c;
    fscanf(stdin, "%d %d %d", &a, &b, &c);
    
    • sscanf() 函数从参数 str 所指向的字符串中读取数据,并将数据进行格式化转换
    char *str = "5454 hello";
    char buf[10];
    int a;sscanf(str, "%d %s", &a, buf);
    

9.4 格式控制字符串 format:输入

  • format 字符串包含一个或多个转换说明,每一个转换说明都是以百分号 “%”,转换说明格式如下
    /*width:最大字符宽度length:长度修饰符,与格式化输出函数的 format 相同type:指定输入数据的类型
    */
    %[*][width][length]type
    %[m][width][length]type
    
  • % 后面可选择性添加星号 * 或字母 m
    • 如果添加了星号*,格式化输入函数会按照转换说明的指示读取输入,但是丢弃输入,意味着不需要对转换后的结果进行存储,所以也就不需要提供相应的指针参数
    • 如果添加了 m,它只能与 %s、%c 以及 %[ 一起使用,调用者无需分配相应的缓冲区来保存格式转换后的数据,原因在于添加了 m,这些格式化输入函数内部会自动分配足够大小的缓冲区,并将缓冲区的地址值通过与该格式转换相对应的指针参数返回出来,该指针参数应该是指向 char* 变量的指针。随后,当不再需要此缓冲区时,调用者应调用 free() 函数来释放此缓冲区
    char *buf;scanf("%ms", &buf);
    ......free(buf);
    
9.4.1 type 类型
  • 此 type 字段与格式化输出函数中的 format 参数的 type 字段是同样的意义,用于指定输入数据的类型
    在这里插入图片描述

在这里插入图片描述

9.4.2 width 最大字符宽度
  • 是一个十进制表示的整数,用于指定最大字符宽度,当达到此最大值或发现不匹配的字符时(以先发生者为准),字符的读取将停止。大多数 type 类型会丢弃初始的空白字符,并且这些丢弃的字符不会计入最大字符宽度。对于字符串转换来说,scanf() 会在字符串末尾自动添加终止符 “\0”,最大字符宽度中不包括此终止符
    scanf("%4s", buf);  // 匹配字符串,字符串长度不超过 4 个字符
    // 用户输入 abcdefg,按回车,那么只能将 adcd 作为一个字符串存储在 buf 数组中
    
9.4.3 length 长度修饰符
  • 与格式化输出函数的格式控制字符串 format 中的 length 字段意义相同,用于对 type 字段进行修饰,扩展识别更多不同长度的数据类型
    scanf("%hd", var);   // 匹配 short int 类型数据
    scanf("%hhd", var);  // 匹配 signed char 类型数据
    scanf("%ld", var);   // 匹配 long int 类型数据
    scanf("%f", var);    // 匹配 float 类型数据
    scanf("%lf", var);   // 匹配 double 类型数据
    scanf("%Lf", var);   // 匹配 long double 类型数据
    

在这里插入图片描述

10. I/O 缓冲

  • 1、首先,应用程序调用标准 I/O 库函数将用户数据写入到 stdio 缓冲区中,stdio 缓冲区是由 stdio 库所维护的用户空间缓冲区
  • 2、然后,针对不同的缓冲模式,当满足条件时,stdio 库会调用文件 I/O(系统调用 I/O)将 stdio 缓冲区中缓存的数据写入到内核缓冲区中,内核缓冲区位于内核空间
  • 3、最终,由内核向磁盘设备发起读写操作,将内核缓冲区中的数据写入到磁盘(或者从磁盘设备读取数据到内核缓冲区)
    在这里插入图片描述

10.1 文件 I/O 的内核缓冲

  • read() 和 write() 系统调用在进行文件读写操作时并不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。如:调用 write() 函数将 5 个字节数据从用户空间内存拷贝到内核空间的缓冲区中
    write(fd, "Hello", 5);  // 写入 5 个字节数据
    
  • 调用 write() 后仅仅只是将这 5 个字节数据拷贝到了内核空间的缓冲区中,拷贝完成之后函数就返回了,在后面的某个时刻,内核会将其缓冲区中的数据写入(刷新)到磁盘设备中
    • 由此可知,系统调用 write() 与磁盘操作并不是同步的,write() 函数并不会等待数据真正写入到磁盘之后再返回
    • 如果在此期间,其它系统调用如 read() 函数读取该文件的这几个字节数据,那么内核将自动从缓冲区中读取这几个字节数据返回给应用程序

    对于读文件而言亦是如此,内核会从磁盘设备中读取文件数据并存储到内核缓冲区中,当调用 read() 读取数据时,read() 调用将从内核缓冲区中读取数据,直至把缓冲区中的数据读完,这时,内核会将文件的下一段内容读入到内核缓冲区中进行缓存,把这个内核缓冲区就称为文件 I/O 的内核缓冲


  • 文件 I/O 的内核缓冲区的设计目的?
    • 1. 提高文件 I/O 的速度和效率(使得系统调用 read()、write()的操作更为快速)
      • 不需要等待磁盘操作(将数据写入到磁盘或从磁盘读取出数据),磁盘操作通常是比较缓慢的
    • 2. 减少内核操作磁盘的次数
      • 如:线程 1 调用 write() 向文件写入 “abcd”,线程 2 也调用 write() 向文件写入 “1234”,这样,数据 “abcd” 和 “1234” 都被缓存在内核缓冲区中,稍后内核会将它们一起写入磁盘中,只发起一次磁盘操作请求

    文件 I/O 的内核缓冲区自然是越大越好,内核会分配尽可能多的内核来作为文件 I/O 的内核缓冲区,但受限于物理内存的总量,操作越大的文件也要依赖于更大空间的内核缓冲区


10.2 刷新文件 I/O 的内核缓冲区

  • 强制将文件 I/O 内核缓冲区中缓存的数据写入(刷新)到磁盘设备中,对于某些应用程序来说是很有必要的
    • 例如,应用程序在进行某操作之前,必须要确保前面步骤调用 write() 写入到文件的数据已经真正写入到了磁盘中,诸如一些数据库的日志进程
    • 在 Ubuntu 系统下拷贝文件到 U 盘时:文件拷贝完成之后,通常在拔掉 U 盘之前,需要执行 sync 命令进行同步操作,这个同步操作其实就是将文件 I/O 内核缓冲区中的数据更新到 U 盘硬件设备,所以如果在没有执行 sync 命令时拔掉 U 盘,很可能就会导致拷贝到 U 盘中的文件遭到破坏
10.2.1 控制文件 I/O 内核缓冲的系统调用
  • fsync() 函数

    • 系统调用 fsync() 将参数 fd 所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync() 函数才会返回
    • 元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数据信息,如:文件大小、时间戳、权限等等信息,这里统称为文件的元数据,这些信息也是存储在磁盘设备中的
    #include <unistd.h>int fsync(int fd);
    
    • 示例
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>#define BUF_SIZE 4096
    #define READ_FILE "./rfile"
    #define WRITE_FILE "./wfile"static char buf[BUF_SIZE];int main(void) {int rfd, wfd;size_t size;/* 打开源文件 */rfd = open(READ_FILE, O_RDONLY);if (rfd < 0) {perror("open error");exit(-1);}/* 打开目标文件 */wfd = open(WRITE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (wfd < 0) {perror("open error");exit(-1);}/* 拷贝数据 */while((size = read(rfd, buf, BUF_SIZE)) > 0)write(wfd, buf, size);/* 对目标文件执行 fsync 同步 */fsync(wfd);/* 关闭文件退出程序 */close(rfd);close(wfd);exit(0);
    }
    
  • fdatasync() 函数

    • 系统调用 fdatasync()与 fsync()类似,不同之处在于 fdatasync() 仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据
    #include <unistd.h>int fdatasync(int fd);
    
  • sync() 函数

    • 系统调用 sync() 会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中,该函数没有参数、也无返回值,意味着它不是对某一个指定的文件进行数据更新,而是刷新所有文件 I/O 内核缓冲区
    #include <unistd.h>void sync(void);
    
10.2.2 控制文件 I/O 内核缓冲的标志
  • O_DSYNC 标志
    • 在调用 open() 函数时,指定 O_SYNC 标志,使得每个 write() 调用都会自动将文件内容数据和元数据刷新到磁盘设备中,其效果类似于在每个 write() 调用之后调用 fsync() 函数进行数据同步
    fd = open(filepath, O_WRONLY | O_SYNC);
    

在程序中频繁调用 fsync()、fdatasync()、sync()(或者调用 open 时指定 O_DSYNC 或 O_SYNC 标志)对性能的影响极大,大部分的应用程序是没有这种需求的

10.3 直接 I/O:绕过内核缓冲

  • Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区,从用户空间直接将数据传递到文件或磁盘设备,把这种操作也称为直接 I/O(direct I/O)或裸 I/O(raw I/O)

    • 例如,某应用程序的作用是测试磁盘设备的读写速率,那么在这种应用需要下,就需要保证 read/write 操作是直接访问磁盘设备,而不经过内核缓冲
    • 对于大多数应用程序而言,使用直接 I/O 可能会大大降低性能
      • 直接 I/O 只在一些特定的需求场合,如:磁盘速率测试工具、数据库系统等
    • 可针对某一文件或块设备执行直接 I/O,需要在调用 open() 函数打开文件时指定 O_DIRECT 标志
    fd = open(filepath, O_WRONLY | O_DIRECT);
    
  • 因为直接 I/O 涉及到对磁盘设备的直接访问,所以在执行直接 I/O 时,必须要遵守三个对齐限制要求

    • 应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐
    • 写文件时,文件的位置偏移量必须是块大小的整数倍
    • 写入到文件的数据大小必须是块大小的整数倍
  • 以上所说的块大小指的是磁盘设备的物理块大小,常见的块大小包括 512 字节、1024 字节、2048 以及 4096 字节,如何确定磁盘分区的块大小?通常,Ubuntu 系统的根文件系统挂载在 /dev/sda1 磁盘分区下

    $ sudo tune2fs -l /dev/sda1 | grep "Block size"
    Block size:               4096
    

10.4 stdio 缓冲

  • 标准 I/O 是 C 语言标准库函数,而文件 I/O 是系统调用,虽然标准 I/O 是在文件 I/O 基础上进行封装而实现,但在效率、性能上,标准 I/O 要优于文件 I/O,原因在于标准 I/O 维护了自己的缓冲区,称为 stdio 缓冲区
    • 文件 I/O 内核缓冲,这是由内核维护的缓冲区,而标准 I/O 所维护的 stdio 缓冲是用户空间缓冲区
    • 当应用程序中通过标准 I/O 操作磁盘文件时,标准 I/O 函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区,然后再一次性将 stdio 缓冲区中缓存的数据通过调用系统调用 I/O(文件 I/O)写入到文件 I/O 内核缓冲区或拷贝到应用程序的 buf 中
10.4.1 对 stdio 缓冲进行设置
1. setvbuf() 函数
  • 调用 setvbuf() 库函数可以对文件的 stdio 缓冲区进行设置,如:缓冲区的缓冲模式、缓冲区的大小、起始地址等

    #include <stdio.h>int setvbuf(FILE *stream, char *buf, int mode, size_t size);
    
  • stream

    • FILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio 缓冲区
  • buf

    • 如果参数 buf 不为 NULL,那么 buf 指向 size 大小的内存区域将作为该文件的 stdio 缓冲区,因为 stdio 库会使用 buf 指向的缓冲区,所以应该以动态(分配在堆内存,如 malloc)或静态的方式在堆中为该缓冲区分配一块空间,而不是分配在栈上的函数内的自动变量(局部变量)
    • 如果 buf 等于 NULL,那么 stdio 库会自动分配一块空间作为该文件的 stdio 缓冲区(除非参数 mode 配置为非缓冲模式)
  • mode:用于指定缓冲区的缓冲类型

    • _IONBF:不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write() 或者 read(),并且忽略 buf 和 size 参数,可以分别指定两个参数为 NULL 和 0。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出
    • _IOLBF:采用行缓冲 I/O。这种情况下,当在输入或输出中遇到换行符 “\n” 时,标准 I/O 才会执行文件 I/O 操作
      • 对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输出换行符时,再将这一行数据通过文件 I/O write() 函数刷入到内核缓冲区中
      • 对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出
    • _IOFBF:采用全缓冲 I/O。这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作(read、write)
      • 对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write() 将 stdio 缓冲区中数据刷入内核缓冲区
      • 对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件常用这种缓冲模式
  • size:指定缓冲区的大小

  • 返回值:成功返回 0,失败将返回一个非 0 值,并设置 errno 指示错误原因

当 stdio 缓冲区中的数据被刷入到内核缓冲区或被读取之后,这些数据就不会存在于缓冲区中了,数据被刷入了内核缓冲区或被读走了

2. setbuf() 函数
  • setbuf() 函数构建于 setvbuf() 之上,执行类似的任务
    #include <stdio.h>void setbuf(FILE *stream, char *buf);
    
  • setbuf() 调用除了不返回函数结果(void)外,就相当于
    // 要么将 buf 设置为 NULL 以表示无缓冲
    // 要么指向由调用者分配的 BUFSIZ 个字节大小的缓冲区(BUFSIZ 定义于头文件 <stdio.h> 中,该值通常为 8192)
    setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
    
3. setbuffer() 函数
  • setbuffer() 函数类似于 setbuf(),但允许调用者指定 buf 缓冲区的大小
    #include <stdio.h>void setbuffer(FILE *stream, char *buf, size_t size);
    
  • setbuffer() 调用除了不返回函数结果(void)外,就相当于
    setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
    
10.4.2 标准输出 printf() 的行缓冲模式测试
  • 标准 printf() 输出测试
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1);
    }
    
    $ gcc io2.c -o io2
    $ ./io2
    Hello World!  # 只有第一个 printf() 打印的信息显示出来了,第二个并没有显示出来

这就是 stdio 缓冲的问题,标准输出默认采用的是行缓冲模式,printf() 输出的字符串写入到了标准输出的 stdio 缓冲区中,只有输出换行符时(不考虑缓冲区填满的情况)才会将这一行数据刷入到内核缓冲区,也就是写入标准输出文件(终端设备)

  • 第一个 printf 包含了换行符,所以已经刷入了内核缓冲区
  • 第二个 printf 没有包含换行符,所以输出的 “Hello World!” 还缓存在 stdio 缓冲区中,需要等待一个换行符才可输出到终端

  • 将标准输出配置为无缓冲模式
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {/* 将标准输出设置为无缓冲模式 */if (setvbuf(stdout, NULL, _IONBF, 0)) {perror("setvbuf error");exit(0);}printf("Hello World!\n");printf("Hello World!");for ( ; ; )sleep(1);
    }
    
    $ gcc io3.c -o io3
    $ ./io3
    Hello World!
    Hello World!
10.4.3 刷新 stdio 缓冲区
  • 无论采取何种缓冲模式,在任何时候都可以使用库函数 fflush() 来强制刷新(将输出到 stdio 缓冲区中的数据写入到内核缓冲区)stdio 缓冲区,该函数会刷新指定文件的 stdio 输出缓冲区
    #include <stdio.h>// 参数 stream 指定需要进行强制刷新的文件,如果该参数设置为 NULL,则表示刷新所有的 stdio 缓冲区
    int fflush(FILE *stream);
    
  • 使用 fflush() 刷新 stdio 缓冲区
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fflush(stdout);  // 刷新标准输出 stdio 缓冲区for ( ; ; )sleep(1);
    }
    
    $ gcc io4.c -o io4
    $ ./io4
    Hello World!
    Hello World!
    

  • 使用库函数 fflush() 是一种强制刷新的手段,在一些其它的情况下,也会自动刷新 stdio 缓冲区
    • 关闭文件时系统自动刷新 stdio 缓冲区
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");fclose(stdout);  // 关闭标准输出for ( ; ; )sleep(1);
    }
    

    上面的测试程序中,在最后都使用了一个 for 死循环,让程序处于休眠状态无法退出,为什么要这样做呢?原因在于程序退出时也会自动刷新 stdio 缓冲区,这样的话就会影响到测试结果,下面去掉 for 死循环,让程序结束

    • 程序退出时系统自动刷新 stdio 缓冲区
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>int main(void) {printf("Hello World!\n");printf("Hello World!");
    }
    
    $ gcc io5.c -o io5
    $ ./io5
    Hello World!
    Hello World! $
    

如果使用 exit()、return 或像上述示例代码一样不显式调用相关函数或执行 return 语句来结束程序,这些情况下程序终止时会自动刷新 stdio 缓冲区,但是如果使用 _exit 或 _Exit() 终止程序则不会自动刷新 stdio 缓冲区

11. 文件描述符与 FILE 指针互转

  • 在同一个文件上执行 I/O 操作时,可以将文件 I/O(系统调用 I/O)与标准 I/O 混合使用,这个时候就需要将文件描述符和 FILE 指针对象之间进行转换,此时可以借助于库函数 fdopen()、fileno() 来完成
    • 库函数 fileno() 可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符
    • 而 fdopen() 则进行着相反的操作
    #include <stdio.h>int fileno(FILE *stream);
    FILE *fdopen(int fd, const char *mode);
    
  • 对于 fileno() 函数来说
    • 根据传入的 FILE 指针得到整数文件描述符,通过返回值得到文件描述符
    • 如果转换错误将返回 -1,并且会设置 errno 来指示错误原因
    • 得到文件描述符之后,便可以使用诸如 read()、write()、lseek()、fcntl()等文件 I/O 方式操作文件
  • fdopen() 函数与 fileno() 功能相反
    • 给定一个文件描述符,得到该文件对应的 FILE 指针
    • 之后便可以使用诸如 fread()、fwrite() 等标准 I/O 方式操作文件了

当混合使用文件 I/O 和标准 I/O 时,需要特别注意缓冲的问题

  • 文件 I/O 会直接将数据写入到内核缓冲区进行高速缓存
  • 标准 I/O 则会将数据写入到 stdio 缓冲区,之后再调用 write() 将 stdio 缓冲区中的数据写入到内核缓冲区
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(void) {printf("print");  // 缺换行符 "\n"write(STDOUT_FILENO, "write\n", 6);exit(0);
}
$ gcc test.c -o test
$ ./test 
write   # 先输出了 "write" 字符串信息,接着再输出了 "print" 字符串信息
print $

相关文章:

Linux系统编程(二):标准 I/O 库(下)

参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 标准 I/O 库简介 标准 I/O 库是指&#xff1a;标准 C 库中用于文件 I/O 操作&#xff08;如&#xff1a;读、写文件等&#xff09;相关的一系列库函数的集合 标准 I/O 库函数相关的函数定义都在头文件 &…...

Mr. Cappuccino的第65杯咖啡——MacOS安装Docker

MacOS安装Docker 下载Docker安装Docker查看Docker相关信息镜像加速 下载Docker Docker官网 Docker文档中心 Docker桌面版下载地址 安装Docker 查看Docker相关信息 docker --versiondocker info镜像加速 阿里云镜像加速器 "registry-mirrors": ["https://gq8…...

解决 Docker Hub 国内无法访问的方法(Docker 镜像下载加速)

参考文章&#xff1a; 知乎&#xff1a;解决目前Docker Hub国内无法访问方法汇总 docker配置 修改配置文件 vim /etc/docker/daemon.json配置内容如下&#xff1a; {"builder": {"gc": {"defaultKeepStorage": "20GB","enab…...

(第61天)多租户架构(CDB/PDB)

背景介绍 Oracle 的 CDB 和 PDB 是 Oracle 12C 及以上版本中引入的新概念,用于管理多租户数据库环境。 Oracle 数据库是商业数据库领域中的翘楚,其强大的功能和高可靠性备受企业用户追捧。而随着云计算和大数据时代的到来,Oracle 也不断推出新的技术以适应这些变化。CDB 技…...

【自定义Source、Sink】Flink自定义Source、Sink对ClickHouse进行读和批量写操作

ClickHouse官网文档 Flink 读取 ClickHouse 数据两种驱动 ClickHouse 官方提供Clickhouse JDBC.【建议使用】第3方提供的Clickhouse JDBC. ru.yandex.clickhouse.ClickHouseDriver ru.yandex.clickhouse.ClickHouseDriver.现在是没有维护 ClickHouse 官方提供Clickhouse JDBC…...

linux 查看服务启动时间

文章目录 linux 查看服务启动时间参数解析 linux 查看服务启动时间 [root104 ~]# ps -o lstart -p ps -ef |grep -v grep |grep "zookeeper"|awk {print$2}STARTED Fri Dec 15 16:54:10 2023参数解析 linux 命令中 ps -ef 详解 ps -ef表示查看全格式的进程。 ps …...

[RK-Linux] 移植Linux-5.10到RK3399(六)| 检查GMAC(RTL8211F)配置使能千兆以太网

ROC-RK3399-PC Pro 使用 RTL8211F PHY 芯片作为以太网收发器。 RTL8211F是一种高性能的千兆以太网物理层收发器(PHY),广泛用于台式机、笔记本电脑、网络交换机等设备中。主要特点: 采用低功耗28nm CMOS技术,功耗低。支持千兆速率(10/100/1000Mbps)。支持全双工和半双工…...

博途WinCC专业版C/S架构入门指南

WinCC Professional V16 支持客户机/服务器架构&#xff0c;但目前只支持单个服务器或单对冗余服务器/多个客户机的模式&#xff0c;还不能支持像WinCC V7.5 SP1中的多个服务器/多个客户机的分布式架构。 博途工控人平时在哪里技术交流博途工控人社群 博途工控人平时在哪里技…...

大数据生态圈kafka在物联网中的应用测试

背景 由物联网项目中使用到了Tbox应用管理车辆&#xff0c;在上报数据的过程中&#xff0c;需要将终端产生的数据通过kafka的produce topic customer对数据进行处理后&#xff0c;放置到mysql中。完成数据二进制到json转换工作。 Kafka的使用 查看kafka的topic ./kafka-topi…...

ChatGPT使用:一个发包机器人的提示词

发包机器人&#xff1a; 设想&#xff1a;目前项目组有n条打包线会输出多个包&#xff0c;用户想获取最新的包是比较困难的&#xff0c;难点在于 1. 分支多&#xff1a;trunk&#xff0c;release&#xff0c;outer等&#xff0c;至少有3个分支&#xff1b; 2. 多平台&#x…...

Axure元件库的使用

1.基本元件库 1.1Axure的画布范围 Axure是一个绘制项目原型图的软件&#xff0c;它里面的基本原件有&#xff1a; 1.1元件的呈现范围 首先我们要了解基本元件的作用范围在哪里&#xff1f; 浏览效果&#xff1a; 可以看出当我们的基本元件放在画布区域内是可以完全呈现出来…...

Unity中Shader URP最简Shader框架(整理总结篇)

文章目录 前言一、精简 ShaderGraph 所有冗余代码后的最简 URP Shader二、我们来对比一下 URP Shader 与 BuildInRP Shader 的对应关系 与 区别1、"RenderPipeline""UniversalPipeline"2、面片剔除、深度测试、深度写入、颜色混合 和 BRP 下一致3、必须引入…...

AT32F435飞控之DIATONE MAMBA MK5 F435 Anti-Interference

AT32F435飞控之DIATONE MAMBA MK5 F435 Anti-Interference 1. 源由2. 规格3. 分析3.1 喜欢3.2 不便3.3 建议 4. 总结5. 参考资料 1. 源由 AT32 F435飞控在xFlight开源飞控之AT32F435计划一文中已经大体阐述了一些移植历史。 之前整体上看&#xff0c;就是航模飞控新MCU的移植…...

ntp时间同步配置中 server、pool和peer的区别

在 NTP&#xff08;Network Time Protocol&#xff09;的配置中&#xff0c;server、pool 和 peer 是用于指定时间同步关系的关键字&#xff0c;它们在角色和行为上有一些区别。 server&#xff1a; server 关键字用于指定一个或多个 NTP 服务器&#xff0c;这些服务器将提供时…...

JMeter安装RabbitMQ测试插件

整体流程如下&#xff1a;先下载AMQP插件源码&#xff0c;可以通过antivy在本地编译成jar包&#xff0c;再将jar包导入JMeter目录下&#xff0c;重启JMeter生效。 Apache Ant 是一个基于 Java 的构建工具。Ant 可用于自动化构建和部署 Java 应用程序&#xff0c;使开发人员更轻…...

基于ssm日用品网站设计论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本日用品网站就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&…...

coco数据集格式的RandomCrop

transforms.py文件的改进 添加 RandomCrop 函数 class RandomCrop(object):"""随机裁剪图像以及bboxes"""def __init__(self, output_size):self.output_size output_sizedef __call__(self, image, target):height, width image.shape[-2:]…...

机器学习-KL散度的直观理解+代码

KL散度 直观理解&#xff1a;KL散度是一种衡量两个分布之间匹配程度的方法。通常在概率和统计中&#xff0c;我们会用更简单的近似分布来代替观察到的数据或复杂的分布&#xff0c;KL散度帮我们衡量在选择近似值时损失了多少信息。 在信息论或概率论中&#xff0c;KL散度&#…...

【教程】制作 iOS 推送证书

​ 目录 证书类型 MAC Key Store 消息推送控制台 制作证书 创建苹果 App ID 使用appuploder制作 .p12文件 创建证书 如需向 iOS 设备推送数据&#xff0c;您首先需要在消息推送控制台上配置 iOS 推送证书。iOS 推送证书用于推送通知&#xff0c;本文将介绍消息推送服务支…...

ToolLLM model 以及LangChain AutoGPT Xagent在调用外部工具Tools的表现对比浅析

文章主要谈及主流ToolLLM 以及高口碑Agent 在调用Tools上的一些对比&#xff0c;框架先上&#xff0c;内容会不断丰富与更新。 第一部分&#xff0c;ToolLLM model 先来说主打Function Call 的大模型们 OpenAI GPT 宇宙第一LLM&#xff0c;它的functionCall都知道&#xff0…...

【MySQL学习之基础篇】约束

文章目录 1. 概述2. 基础约束3. 外键约束3.1. 介绍3.2. 外键的添加3.3. 外键删除和更新行为 1. 概述 概念&#xff1a; 约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。     目的&#xff1a; 保证数据库中数据的正确、有效性和完整性。 分类&#x…...

【DataSophon】大数据管理平台DataSophon-1.2.1基本使用

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…...

基于redisson实现发布订阅(多服务间用避坑)

前言 今天要分享的是基于Redisson实现信息发布与订阅&#xff08;以前分享过直接基于redis的实现&#xff09;&#xff0c;如果你是在多服务间基于redisson做信息传递&#xff0c;并且有服务压根就收不到信息&#xff0c;那你一定要看完。 今天其实重点是避坑&#xff0…...

Java 源码、反码、补码 位运算

文章目录 1. 源码、反码、补码1.1 原码1.2 反码1.3 补码1.4 byte的最大值1.5 byte的最小值 2. 位运算2.1 & 与2.2 | 或2.3 ~ 非2.4 ^ 异或2.5 << 左移 &#xff08;没有无符号左移&#xff09;2.6 >> 右移 &#xff08;有符号右移&#xff09;2.7 >>>…...

时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解

时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解 目录 时序分解 | Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICEEMDAN时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现NGO-ICEEMDAN基于北方苍鹰算法优化ICE…...

Linux Conda 安装 Jupyter

在Linux服务器Conda环境上安装Jupyter过程中遇到了无数的报错&#xff0c;特此记录。 目录 步骤一&#xff1a;安装Anaconda3 步骤二&#xff1a;配置Conda源 步骤三&#xff1a;安装Jupyter 安装报错&#xff1a;simplejson.errors.JSONDecodeError 安装报错&#xff1a;…...

金融众筹系统源码:适合创业孵化机构 附带完整的搭建教程

互联网技术的发展&#xff0c;金融众筹作为一种新型的融资方式&#xff0c;逐渐成为创业孵化机构的重要手段。为了满足这一需求&#xff0c;金融众筹系统源码就由此而生&#xff0c;并附带了完整的搭建教程。 以下是部分代码示例&#xff1a; 系统特色功能一览&#xff1a; 1.…...

OpenCV imencode 函数详解与应用示例

OpenCV imencode 函数详解与应用示例 介绍imencode 函数的基本信息示例代码应用场景 介绍 OpenCV是一个强大的计算机视觉库&#xff0c;提供了许多图像处理和分析的工具。imencode函数是其中之一&#xff0c;用于将图像编码为指定格式的字节流。这个函数对于图像的存储、传输和…...

持续集成交付CICD:Jenkins使用CD流水线下载Nexus制品

目录 一、实验 1.Jenkins使用CD流水线下载Nexus制品 一、实验 1.Jenkins使用CD流水线下载Nexus制品 &#xff08;1&#xff09;Jenkins新建CD流水线 &#xff08;2&#xff09;新建视图 &#xff08;3&#xff09;查看视图 &#xff08;4&#xff09;添加字符参数 &#xf…...

【C++】输入输出流 ⑩ ( 文件流 | 文件流打开方式参数 | 文件指针 | 组合打开方式 | 文件打开失败 )

文章目录 一、文件流打开方式参数1、文件流打开方式参数2、文件指针3、组合打开方式4、文件打开失败 一、文件流打开方式参数 1、文件流打开方式参数 文件流打开方式参数 : ios::in : 以只读方式打开文件 ;ios::out : 以只写方式打开文件 , 默认打开方式 , 如果文件已存在则清…...

紧急通知界面访问升级中狼人/seo流量优化

描述 给一组整数&#xff0c;问能找出多少对整数&#xff0c;他们的和大于一个给定的目标值。 使用 O(1) 的额外空间和 O(nlogn) 的时间。 您在真实的面试中是否遇到过这个题&#xff1f; 是 样例 对于 numbers [2, 7, 11, 15], target 24 的情况&#xff0c;返回 1。因…...

简单网站建设/网站查询工具

管道的半双工 管道在双方在内核中共用同一内存区&#xff0c;为了保证数据信息的准确性&#xff0c;所以双方进程读写互斥&#xff0c;且一方写时另一方不可读。 由于双方共享一块缓冲&#xff0c;所以半双工的限制就产生了。 Socket的双工 所谓双工就是两个进程拥有两块缓存…...

港口建设申报网站/网络营销seo优化

做好笔记&#xff0c;打好基础&#xff0c;往高处走。供自己参考&#xff0c;同时欢迎大家指正。1、在官网下载好新版的免安装文件&#xff0c;我的是5.7.20。解压到自定义目录。2、配置环境变量&#xff1a;右键计算机-->属性-->高级系统设置-->环境变量 在path里最…...

长沙工程有限公司/企业seo顾问

“互联网这个行业&#xff0c;最早由老虎和狐狸把事业开创起来&#xff0c;后来逐渐进入两条狗&#xff0c;然后是熊——百度&#xff0c;还有猫——猫扑。当然&#xff0c;南极的动物企鹅也不甘寂寞&#xff0c;没有想到 在这么多野兽横行的市场上竟然长出土豆&#xff0c;最后…...

廊坊哪里有做网站的/网络推广是干嘛的

一个快捷方式后面跟着快捷方式几个字感觉不太舒服&#xff0c;解决办法很简单。不用任何工具&#xff0c;直接修改注册表中资源管理器&#xff08;explorer&#xff09;中选项link的键值为零即可。具体操作如下: 1、按win键R打开运行窗口&#xff0c;在里面输入regedit进入注册…...

苹果地图可以看街景吗/广东网站营销seo费用

var scrollTop; var strTop window.location.href;//多个页面的记忆功能&#xff0c;需要通过页面地址来分辨cookie名称 $(document).scroll(function () {   scrollTop $(document).scrollTop(); //获取滚动条位置   $.cookie(strTop,scrollTop,{ expires: 1 });生成coo…...