标准IO
IO缓冲的必要性
所有磁盘都是以块作为基本操作单元,因此IO要求最好数据大小与块对齐。对齐影响的为BIO部分,而不是写页缓存,即写页缓存时,系统不会管是否快对齐,只有pdflush时,数据块加入BIO队列,内核才会填补数据以块对齐。所以页缓存一定程度上缓解了块对齐的问题,导致write和fwrite的更大差异在于系统调用的消耗。因此,应用程序的IO操作数据大小若没有块对齐,则内核会进行冗余操作保证块对齐,导致IO性能严重下降。内核通过延迟写,合并同块IO请求,预读等操作 ,以提高性能。用户空间缓冲IO也是为了提高性能。
为了保证IO操作对齐块,IO操作的数据大小应为块的整数倍如4096。所以预先分配一个 块整数倍大小的 buffer,当写输入时,缓冲到buffer中,直到数据到达一定长度(可能没有满,但是将多次 IO修补 变成一次 IO修补),再调用系统调用,将数据交给内核。当读数据时,一次尽量读 buffer大小,再从buffer中读取数据。
应用程序可以自己实现缓冲机制,也可以使用标准IO。
标准IO函数
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
参数
filename-- 这是 C 字符串,包含了要打开的文件名称。
mode-- 这是 C 字符串,包含了文件访问模式。
功能
使用给定的模式mode打开filename所指向的文件。
返回值
文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回 NULL,并把错误代码存在error中。
一般而言,打开文件后会做一些文件读取或写入的动作,若打开文件失败,接下来的读写动作也无法顺利进行,所以一般在 fopen() 后作错误判断及处理。
Mode | 描述 | 文件状态 |
---|---|---|
“r” | 打开文件仅供读取 | 必须存在 |
“r+” | 打开文件供读取并写入 | 必须存在 |
“w” | 创建新文件仅供写入 | 若存在,则清空后再写入 |
“w+” | 创建新文件供读取并写入 | 若存在,则清空后再写入 |
“a” | 打开文件附加写入 | 若不存在,则创建新文件写入 |
“a+” | 打开文件读取并附加写入 | 若不存在,则创建新文件写入 |
mode虽然还有b,但是linux忽略此值,linux以相同方式处理文本文件和二进制文件。
int fgetc(FILE *stream)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。
功能
从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
返回值
该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
fgetc返回值必须保存再int变量中,将他存放在char变量是常见但危险的错误。
int ungetc(int char, FILE *stream)
参数
char -- 这是要被推入的字符。该字符以其对应的 int 值进行传递。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了输入流。
功能
把字符 char(一个无符号字符)推入到指定的流 stream 中,以便它是下一个被读取到的字符。
返回值
如果成功,则返回被推入的字符,否则返回 EOF,且流 stream 保持不变。
char *fgets(char *str, int n, FILE *stream)
参数
str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
功能
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
返回值
如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。如果发生错误,返回一个空指针。
读取size-1个字节,并存储到str,最后加上\0,读到EOF或一个newline后,便会停止读取动作。如果读到newline字符,则\n被存入str。可以用 fgetc代替fgets,尽管重复调用 fgetc,会导致一定开销,但相对于IO对齐,所以这个开销并不大。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
ptr -- 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size -- 这是要读取的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。
功能
从给定流 stream 读取数据到 ptr 所指向的数组中。
返回值
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。
读取 size * nmenb 个字节,使用此函数,用于读取二进制数据。
int fputc(int char, FILE *stream)
参数
char -- 这是要被写入的字符。该字符以其对应的 int 值进行传递。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。
功能
把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。
返回值
如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。
将字符char转换成unsigned char ,写入stream。
int fputs(const char *str, FILE *stream)
参数
str -- 这是一个数组,包含了要写入的以空字符终止的字符序列。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
功能
把字符串写入到指定的流 stream 中,但不包括空字符。
返回值
该函数返回一个非负值,如果发生错误则返回 EOF。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
ptr -- 这是指向要被写入的元素数组的指针。
size -- 这是要被写入的每个元素的大小,以字节为单位。
nmemb -- 这是元素的个数,每个元素的大小为 size 字节。
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
功能
将 ptr 所指向的数组中的数据写入到给定流 stream 中。
返回值
如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。
long int ftell(FILE *stream)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
功能
返回给定流 stream 的当前文件位置。
返回值
该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。
int fseek(FILE *stream, long int offset, int whence)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
offset -- 这是相对 whence 的偏移量,以字节为单位。
whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
SEEK_SET 文件的开头
SEEK_CUR 文件指针的当前位置
SEEK_END 文件的末尾
功能
设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
返回值
如果成功,则该函数返回零,否则返回非零值。
int fprintf(FILE *stream, const char *format, ...)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
format -- 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision]
功能
发送格式化输出到流 stream 中。
返回值
如果成功,则返回写入的字符总数,否则返回一个负数。
int sprintf(char *str, const char *format, ...)
参数
str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
format -- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier
功能
发送格式化输出到 str 所指向的字符串。
返回值
如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
int feof(FILE *stream)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
功能
测试给定流 stream 的文件结束标识符。
返回值
当设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回零。
int fclose(FILE *stream)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流。
功能
关闭流 stream。刷新所有的缓冲区。
返回值
如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。
缓存控制机制
void setbuf(FILE *stream, char *buffer)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
buffer -- 这是分配给用户的缓冲,它的长度至少为 BUFSIZ 字节,BUFSIZ 是一个宏常量,表示数组的长度。
功能
定义流 stream 应如何缓冲。该函数应在与流 stream 相关的文件被打开时,且还未发生任何输入或输出操作之前被调用一次。
线程安全
标准IO具备线程安全。在内部,它有一个锁(lock)和锁计数(lock count)。因此单个函数执行环境内,标准IO是原子的。
但是许多程序希望的原子性大于函数调用层次。如应用希望多个写操作都能原子。为此标准IO提供了一系列函数。
void flockfile(FILE *stream);
void funlockfile(FILE *stream);
int ftrylockfile(FILE *stream);
标准IO存在的问题
标准IO最大的问题: 性能受到两次复制的影响。即内核页缓存 到 标准IO缓冲,标准IO缓冲 到 用户提供buf。
改进方法:
读操作:读返回一个指向标准IO缓冲区的指针,用户可以直接操作缓冲区里的数据,用户自己决定是否复制这些数据
写操作:调用写操作时,不复制数据,只记录指针,当准备刷新数据到内核时,扫描存储的指针列表,以写出数据,使用 writev 实现。