目录
1.什么是文件缓冲区
2.为什么要设计一个文件缓冲区
3.文件缓冲区刷新的三种方式
4.证明文件缓冲区的存在
1.什么是文件缓冲区
在我们使用C语言提供的文件IO接口进行文件操作的时候,是不是要通过一个 FILE* 类型的结构体指针来操作文件,这个 FILE 结构体当中就维护了一块内存区域,我们称之为文件缓冲区。
所以:文件缓冲区就是C语言标准库为打开的文件流提供的一块内存区域。
我们可以查看/usr/include/libio.h这个文件,找到这部分内容,其中就包含了缓冲区相关的指针字段:
struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封装的文件描述符#if 0int _blksize;#elseint _flags2;#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock;#ifdef _IO_USE_OLD_IO_FILE
};
2.为什么要设计一个文件缓冲区
我们可以 以搬东西为例来理解:在大学生活中,你可能经历过搬寝室这么一件事,我们在将物品从就寝室搬迁到新寝室的时候,会不会一样一样物品的搬?大概率不会。聪明的你可能会找宿管阿姨借一两拖车,把自己的东西打包整理好,直接一拖车就拖过去了,省时又省力。
文件缓冲区的设计也是为了解决同样的问题。我们知道,C语言进行文件IO的接口底层都分装了操作系统提供的系统调用接口,而系统调用接口可能直接或间接的需要和外设进行数据交换,内存和外设之间的数据交换速度是很慢的,所以,调用系统调用是有时间和空间成本的。所以,在能够达到相同目的的情况下,系统调用接口调用的越少越好。
于是,C语言便给所有打开的文件流提供了一个文件缓冲区,用来暂存数据,当文件缓冲区中的数据积累到一定的量的时候再调用系统调用将数据刷新到操作系统内核提供的缓冲区,操作系统内核提供的缓冲区中的内容由操作系统自己决定什么时候刷新到设备上。于是,当文件缓冲区中的内容刷新到系统级别的缓冲区中之后,我们用户层就可以认为,数据刷新到外设中了。
文件缓冲区和内核缓冲区进行数据流动的流程图:
所以,文件缓冲区是一种提高用户进行文件io效率的有效手段。
3.文件缓冲区刷新的三种方式
在上面我们提到了数据从文件缓冲区中进行刷新的概念,其实就是文件缓冲区中的数据被剪切到内核缓冲区中。
文件缓冲区中数据的正常刷新方式有以下三种:
- 无刷新or无缓冲:数据立即进行IO操作,stderr通常是无缓冲的。
- 行刷新or行缓冲:遇到换行符时 进行IO操作,向显示器进行写入通常是行缓冲的。
- 全刷新or全缓冲:文件缓冲区被写满才进行文件操作,向普通文件进行写入通常是全缓冲的。
其他特殊的刷新方式:
- 强制刷新,我们可以通过C标准库提供的 fflush 函数(int fflush(FILE *stream);)进行强制刷新。
- 进程退出的时候,会自动刷新文件缓冲区。
4.证明文件缓冲区的存在
我们编写如下代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main()
{// msg0 和 msg1 是被C语言提供的接口打印的// msg2 是被系统调用接口打印的const char *msg0="hello printf\n";const char *msg1="hello fwrite\n";const char *msg2="hello write\n";// C语言提供的接口printf("%s", msg0);fwrite(msg1, strlen(msg0), 1, stdout);// 系统调用接口write(1, msg2, strlen(msg2));// 创建一个子进程fork();return 0;
}
第一种情况
直接运行代码,结果如下:
第二种情况
我们将该程序的输出重定向到一个文件中,并查看文件的内容:
- 我们看到:使用系统调用打印的语句只执行了一次,而使用C语言接口打印的语句打印了两次。
现象解释:
当我们直接运行代码的时候。数据是向显示器输入的,文件缓冲区的刷新方式是行缓冲,当调用C语言接口打印的语句,遇到换行符的时候直接就将数据刷新到显示器上了;而调用系统调用打印的语句直接接输出到内核缓冲区中了,相当于直接输出到显示器上,于是我们看到了第一种情况。
当我们将代码运行结果重定向到 file.txt 文件中的时候。文件缓冲区的刷新方式就变成了全缓冲,这对于调用系统调用打印的数据没有影响,调用系统调用打印的数据相当于直接输出到文件中了;但是,调用C语言接口打印的两条语句不足以写满文件缓冲区,于是,这两条语句一直待在父进程的文件缓冲区中,当父子进程都没退出的时候,又没有进程对数据进行修改,父子进程看到同一个文件缓冲区;但是,父子进程中的任何一个退出的时候,都会自动刷新文件缓冲区,刷新文件缓冲区会清空文件缓冲区,清空文件缓冲区也是修改数据的一种情况,于是该进程立马发生写时拷贝,文件缓冲区中的数据父子进程各自私有一份;之后,两个进程退出的时候,都将各自文件缓冲区中的数据刷新到 file.txt 文件当中。于是,我们就看到了 通过系统调用打印的语句只打印了一次,而通过C语言接口打印的语句打印了两次。
至此,我们通过代码验证了文件是有缓冲区的。