标准I/O库
1、标准I/O库简介
什么是标准I/O库
- 标准C库当中用于文件I/O操作相关的一套库函数,使用标准I/O需要包含头文件
标准I/O和文件I/O之间的区别
-
标准I/O是库函数,而文件I/O是系统调用
-
标准I/O是对文件I/O的封装
-
可移植性:标准I/O相对于文件I/O具有更好的额移植性
-
效率:标准I/O在效率上要优于文件I/O
2、FILE 指针和fopen函数
FILE指针
- 标准I/O使用FLIE指针作为文件句柄,与文件I/O中的文件描述符相似
打开文件–fopen()函数
-
标准I/O中使用fopen()函数打开文件,是对open函数的封装
-
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);-
path:参数 path 指向文件路径,可以是绝对路径、也可以是相对路径
-
mode:参数 mode 指定了对该文件的读写权限
-
返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno
-
新建文件的权限
-
无法手动指定文件的权限,但却有一个默认值:
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)(110 110 110)- 所有用户都只有可读可写权限,没有可执行权限
-
-
关闭文件–fclose()函数
-
关闭一个由 fopen()打开的文件
-
#include <stdio.h>
int fclose(FILE *stream);-
stream 为 FILE 类型指针
-
调用成功返回 0;失败将返回 EOF(也就是-1),并且会设置 errno
-
3、读文件和写文件
读文件–fread()函数
-
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);1-
ptr:fread()将读取到的数据存放在参数 ptr 指向的缓冲区中
-
size:fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节
-
nmemb:参数 nmemb 指定了读取数据项的个数
-
stream:FILE 指针
-
返回值:调用成功时返回读取到的数据项的数目nmemb,如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,可以使用 ferror()或 feof()函数来判断
-
写文件–fwrite()函数
-
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);-
ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中
-
size:参数 size 指定了每个数据项的字节大小
-
nmemb:参数 nmemb 指定了写入的数据项个数
-
stream:FILE 指针
-
返回值:调用成功时返回读取到的数据项的数目nmemb,如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)
-
设置读写位置–fseek()函数
-
用于设置文件读写位置偏移量
-
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);-
stream:FILE 指针
-
offset:偏移量,以字节为单位
-
用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
-
SEEK_SET
- 读写偏移量将指向 offset 字节位置处(从文件头部开始算)
-
SEEK_CUR
- 读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负
-
SEEK_END
- 读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负
-
-
返回值:成功返回 0;发生错误将返回-1,并且会设置 errno
-
与 lseek()函数的返回值意义不同
-
lseek()函数返回值
-
成功将返回从文件头部开始算起的位置偏移量(字节为单位)
-
错误将返回-1
-
-
-
-
4、feof和ferror函数
判断是否到达文件末尾–feof()函数
-
当文件的读写位置移动到了文件末尾时,end-of-file 标志将会被设置
-
#include <stdio.h>
int feof(FILE *stream);- 如果 end-of-file 标志被设置了,则调用
feof()函数将返回一个非零值,如果 end-of-file 标志没有被设置,则返回 0
- 如果 end-of-file 标志被设置了,则调用
判断是否发生错误–ferror()函数
-
当对文件的 I/O 操作发生错误时,错误标志将会被设置
-
#include <stdio.h>
int ferror(FILE *stream);- 如果错误标志被设置了,则 调用 ferror()函数
将返回一个非零值,如果错误标志没有被设置,则返回 0
- 如果错误标志被设置了,则 调用 ferror()函数
清除标志–clearerr()函数
-
用于清除 end-of-file 标志和错误标志
-
当调用 feof()或 ferror()校验这些标志后,通常需 要清除这些标志,避免下次校验时使用到的是上一次设置的值,此时可以手动调用 clearerr()函数清除标志
-
对于 end-of-file 标志,当调用 fseek()成功时也会清除文件的 end-of-
file 标志
-
-
#include <stdio.h>
void clearerr(FILE *stream);- 此函数没有返回值,调用将总是会成功!
5、格式化I/O
格式化输出
-
printf()
-
用于将格式化数据写入到标准输出
-
int printf(const char *format, …);
- 函数调用成功返回打印输出的字符数;失败将返回一个负值!
-
-
fprintf()
-
int fprintf(FILE *stream, const char *format, …);
-
函数调用成功返回写入到文件中的字符数;失败将返回一个负值!
-
-
dprintf()
-
int dprintf(int fd, const char *format, …);
-
函数调用成功返回写入到文件中的字符数;失败将返回一个负值!
-
-
sprintf()
-
int sprintf(char *buf, const char *format, …);
-
一般会使用这个函数进行格式化转换
-
会在字符串尾端自动加上一个字符串终止字符’\0’
-
可能会造成由参数 buf 指定的缓冲区溢出,调用者有责任确保该缓冲区足够大,因为缓冲区溢出会造成程序不稳定甚至安全隐患!
-
函数调用成功返回写入到 buf 中的字节数;失败将返回一个负值!
-
-
snprintf()
-
int snprintf(char *buf, size_t size, const char *format, …);
-
使用参数 size 显式的指定缓冲区的大小,如果写入到缓冲区的字节数大于参数 size 指定的大小,超出的部分将会被丢弃!如果缓冲区空间足够大,snprintf()函数就会返回写入到缓冲区的字符数,与 sprintf()函数相同,也会在字符串末尾自动添加终止字符’\0’
-
若发生错误,snprintf()将返回一个负值!
-
格式化输入
-
scanf()
-
将用户输入(标准输入)的数据进行格式化转换
-
int scanf(const char *format, …);
-
函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值。
-
-
fscanf()
-
从 FILE 指针指定文件中读取数据,并将数据进行格式化转换
-
int fscanf(FILE *stream, const char *format, …);
-
函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值
-
-
sscanf()
-
从参数 str 所指向的字符串中读取数据,并将数据进行格式化转换
-
int sscanf(const char *str, const char *format, …);
-
函数调用成功后,将返回成功匹配和分配的输入项的数量;如果较早匹配失败,则该数目可能小于所提供的数目,甚至为零。发生错误则返回负值。
-
6、文件I/O的内核缓冲
文件I/O的内核缓冲
- 系统调用 write() 与磁盘操作并不是同步的
- 提高文件 I/O 的速度和效率
- Linux内核对于文件I/O的内核缓冲区大小没有固定上限,因此内核会尽可能分配更多的内存作为文件I/O的内核缓冲区。系统可用的物理内存越多,内核缓冲区也会相应变大,这样操作更大的文件时就能依赖更大空间的内核缓冲区。
刷新文件I/O的内核缓冲
-
系统调用(fsync、fdatasync、sync)
-
fsync()函数
-
将参数 fd 所指文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回
-
#include <unistd.h>
int fsync(int fd);- fd 表示文件描述符,函数调用成功将返回 0,失败返回-1 并设置 errno
-
元数据
- 元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数
据信息,譬如文件大小、时间戳、权限等等信息,这些信息存储在磁盘设备中
- 元数据并不是文件内容本身的数据,而是一些用于记录文件属性相关的数
-
-
fdatasync()函数
-
fdatasync()与 fsync()类似,不同之处在于 fdatasync()仅将参数 fd 所指文件的内容数据写入磁盘,并不包括文件的元数据;同样,只有在对磁盘设备的写入操作完成之后,fdatasync()函数才会返回
-
#include <unistd.h>
int fdatasync(int fd);
-
-
sync()函数
-
将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中
-
#include <unistd.h>
void sync(void); -
在 Linux 实现中,调用 sync()函数仅在所有数据已经写入到磁盘设备之后才会返回;然后在其它系统中,sync()实现只是简单调度一下 I/O 传递,在动作未完成之后即可返回
-
-
-
O_DSYNC和O_SYNC标志 控制文件 I/O 内核缓冲标志
-
O_DSYNC 标志
- 当在调用open()函数时指定O_DSYNC标志,这个标志的作用类似于在每次调用write()函数后立即调用fdatasync()函数来进行数据同步。这意味着在使用O_DSYNC标志的情况下,系统会确保每次写入操作后都会将数据同步到磁盘,实现数据同步
-
O_SYNC 标志
- 在调用 open()函数时,指定 O_SYNC 标志,使得每个 write()调用都会自动将文件内容数据和元数据刷新到磁盘设备中,其效果类似于在每个 write()调用之后调用 fsync()函数进行数据同步
-
7、直接I/O-绕过内核缓冲
O_DIRECT标志
-
在调用 open()函数打开文件时,指定
O_DIRECT 标志就可以实现直接I/O -
如:fd = open(filepath, O_WRONLY | O_DIRECT);
-
需要在程序中自己指定 O_DIRECT 标志
-
直接在源文件中定义:#define _GNU_SOURCE
-
gcc 编译时使用-D 选项定义_GNU_SOURCE 宏
-
gcc -D_GNU_SOURCE -o testApp testApp.c
-
该宏定义在整个源码工程中都是生效的,是一个全局宏定义
-
-
直接I/O的对齐限制
-
1、应用程序的缓冲区起始地址必须以块大小的整数倍进行对齐
-
定义了一个静态字符数组
-
其大小为块的整数倍字节
-
指定数组在内存中的对齐方式为块大小个字节
- 通过__attribute((aligned (块大小)))这个属性
-
如:static char buf[8192] __attribute((aligned (4096)));
-
-
-
2、写文件时,文件的位置偏移量必须是块大小的整数倍
- 调用lseek()函数来移动读写指针
-
3、写入到文件的数据大小必须是块大小的整数倍
- 先判断写入字节
-
不满足3 个对齐条件种的任何一个,然后编译程序进行测试,会发生 write()函数会报错,均是“Invalid argument”错误。
对比测试
- 直接I/O方式的效率和性能较低,大多数应用程序不会使用直接I/O方式来进行文件I/O操作,通常只在一些特殊的应用场景下才会考虑使用。然而,我们可以利用直接I/O方式来测试磁盘设备的读写速率,因为这种测试方式相比普通I/O方式更加准确。
8、stdio缓冲
标准I/O的stdio缓冲
-
标志I/O在应用层维护了自己的缓冲区,把这个缓冲区称为stdio缓冲
-
标准 I/O(fopen、fread、fwrite、fclose、fseek 等)是 C 语言标准库函数
-
文件 I/O(open、read、write、
close、lseek 等)是系统调用
-
stdio缓冲的缓冲类型
-
1、无缓冲_IONBF
- 不对 I/O 进行缓冲(无缓冲)。意味着每个标准 I/O 函数将立即调用 write()或者 read(),并且忽略 buf 和 size 参数,可以分别指定两个参数为 NULL 和 0。标准错误 stderr 默认属于这一种类型,从而保证错误信息能够立即输出。
-
2、行缓冲_IOLBF
- 采用行缓冲 I/O。在这种情况下,当在输入或输出中遇到换行符"\n"时,标准 I/O 才会执
行文件 I/O 操作。对于输出流,在输出一个换行符前将数据缓存(除非缓冲区已经被填满),当输出换行符时,再将这一行数据通过文件 I/O write()函数刷入到内核缓冲区中;对于输入流,每次读取一行数据。对于终端设备默认采用的就是行缓冲模式,譬如标准输入和标准输出。
- 采用行缓冲 I/O。在这种情况下,当在输入或输出中遇到换行符"\n"时,标准 I/O 才会执
-
3、全缓冲_IOFBF
- 采用全缓冲 I/O。在这种情况下,在填满 stdio 缓冲区后才进行文件 I/O 操作(read、write)。对于输出流,当 fwrite 写入文件的数据填满缓冲区时,才调用 write()将 stdio 缓冲区中的数据刷入 内核缓冲区;对于输入流,每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件默认常用这种缓冲模式。
对stdio缓冲进行设置
-
setvbuf()函数
-
对文件的 stdio 缓冲区进行设置,譬如缓冲区的缓冲模式、缓冲区的大小、起始地址等
-
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);-
stream:FILE 指针,用于指定对应的文件,每一个文件都可以设置它对应的 stdio 缓冲区
-
如果参数buf不为NULL,则buf指向大小为size的内存区域将被用作该文件的stdio缓冲区。因为stdio库会使用buf指向的缓冲区,所以应该以动态方式(在堆内存中分配,比如使用malloc)或静态方式在堆中为该缓冲区分配空间,而不是在栈上分配函数内的自动变量(局部变量)。
-
如果buf等于NULL,则stdio库会自动分配一块空间作为该文件的stdio缓冲区(除非参数mode配置为非缓冲模式)
-
mode:参数 mode 用于指定缓冲区的缓冲类型
-
size:指定缓冲区的大小
-
返回值:成功返回 0,失败将返回一个非 0 值,并且会设置 errno
-
-
当stdio缓冲区中的数据被刷新到内核缓冲区或被读取后,这些数据就不再存在于缓冲区中,而是被刷新到了内核缓冲区或被读取走了。
-
-
setbuf()函数
-
setbuf()函数构建与 setvbuf()之上,执行类似的任务
-
#include <stdio.h>
void setbuf(FILE *stream, char *buf); -
setbuf()调用除了不返回函数结果(void)外,就相当于:
-
setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
-
要么 将 buf 设置为 NULL 以表示无缓冲 ,要么指向由调 用者分配 的 BUFSIZ 个字节大小的 缓冲区
(BUFSIZ 定义于头文件<stdio.h>中,该值通常为 8192)
-
-
-
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);
-
刷新stdio缓冲
-
无论采取何种缓冲模式,在任何时候都可以使用库函数fflush()来强制刷新(将输出到stdio缓冲区中的数据写入到内核缓冲区,通过write()函数)stdio缓冲区。该函数会刷新指定文件的stdio输出缓冲区。
-
#include <stdio.h>
int fflush(FILE *stream);-
stream 指定需要进行强制刷新的文件,如果该参数设置为 NULL,则表示刷新所有的 stdio 缓冲区
-
调用成功返回 0,否则将返回-1,并设置 errno
-
-
调用fclose()函数也会去刷新stdio缓冲
-
-
程序退出时刷新 stdio 缓冲区
9、I/O缓冲小结
10、文件描述符与FILE指针互转
fdopen()函数
-
可以将文件 I/O 中所使用的文件描述符转换为标准 I/O 中使用的 FILE 指针
-
FILE *fdopen(int fd, const char *mode);
-
返回值得到文件对应的 FILE 指针
-
mode参数与文件描述符 fd 的访问模式不一致,则会导致调用 fdopen()失败,返回NULL
-
fileno()函数
-
可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符
-
int fileno(FILE *stream);
-
返回值得到文件描述符
-
如果转换错误将返回-1,并且会设置 errno
-