有关缓冲区的概念和 C 语言中相关的内容...
在了解二者联系之前,先要明白缓冲区的概念。
缓冲区
所谓缓冲区(Buffer),就是指暂时存放输入或输出内容的一个内存空间,并且这个空间的大小是一定的。
那为什么要设置缓冲区呢?
目的是为了将高速读写设备与低速读写设备同步起来。举个简单的例子,打印机的打印速度比较慢,而电脑操作指令的响应速度比较块,但在打印机打印的同时,可以操作电脑不断把要打印的内容发送到打印机(电脑操作不用等待),对应打印机就会依次执行打印任务。
另外,需要注意区分缓冲区(Buffer)和缓存(Cache)的概念,这里不做深究。
C
在 C 语言中也有用到缓冲区这个“设计模式”,之所以称之为设计模式,一是笔者不确定其他语言是否也是如此(只能确定大部分语言是如此);二是就笔者的个人理解,将缓冲区称呼为一种(计算机领域的)设计模式更恰当,因为不止程序语言的设计如此,部分硬件的设计也是有这个模式的存在。
不过,这里不深究其他的内容,只专注 C 语言中与缓冲区相关的内容。
C 语言中存在三种缓冲模式:全缓冲(fully buffered)、行缓冲(line buffered)和无缓冲(unbuffered)。
在探讨各种缓冲模式之前,先得明确一下 C 语言中流的概念,可以参考一下百度百科——流。实际上,可以把 C 语言中通过fopen
函数打开文件得到的文件指针看作流的入口,当通过这个文件指针向文件读写数据时,这些数据就是流,这个过程就是“流动”。
对应的,C 语言中提供了三个标准流:标准输入流(stdin
)、标准输出流(stdout
)和标准错误流(stderr
),其中stdin
和stdout
一般是行缓冲,stderr
一般是无缓冲的(为的是能第一时间输出错误信息)。
写到这里,会有一个疑问,使用fopen
函数打开的文件流,默认的缓冲模式是什么呢?一般是行缓冲,可能是全缓冲,这取决于编译器具体的实现,但可以通过setvbuf
函数来修改流的缓冲模式。
好了,其他的问题暂时不谈了,下面来探究一下各种缓冲模式。
PS:以下测试均在 Ubuntu 16.04 环境下进行。
全缓冲
全缓冲就是在缓冲区填满了之后,才执行 I/O 操作。测试代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const char *string = "ABCDEFG";
int main() {
char buff[1024];
FILE *fp = NULL;
fp = fopen(FILEPATH, "w+");
if(fp == NULL) {
perror("fopen() error!\n");
}
int ret = 0;
ret = setvbuf(fp, buff, _IOFBF, 1024);
if(ret != 0) {
perror("setvbuf error!\n");
}
fprintf(fp, "%s", string);
while(1){
}
return 0;
}
在上面的代码中,首先使用fopen
函数在w+
(读,若文件不存在则创建)的模式下打开文件,接着使用setvbuf
函数设置缓冲模式为_IOFBF
(全缓冲)。运行程序,在另一个终端中可以确认tmp
文件已创建,然后键入1
cat tmp
显示文件无内容。
行缓冲
行缓冲就是在遇到换行符(\n
)时,执行 I/O 操作,典型代表就是标准输入的scanf
函数,每次输入完数据和回车后,才能看到效果。测试代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const char *string = "ABCDEFG\n";
int main() {
char buff[1024];
FILE *fp = NULL;
fp = fopen(FILEPATH, "w+");
if(fp == NULL) {
perror("fopen() error!\n");
}
int ret = 0;
ret = setvbuf(fp, buff, _IOLBF, 1024);
if(ret != 0) {
perror("setvbuf error!\n");
}
fprintf(fp, "%s", string);
while(1){
}
return 0;
}
相比 test1,上面的代码有两个改动:
const char *string = "ABCDEFG\n";
,加入\n
后,程序才会进行 I/O 操作ret = setvbuf(fp, buff, _IOLBF, 1024);
,修改为行缓冲模式
同样的思路,运行程序,在另一个终端中可以确认tmp
文件已创建,然后键入1
cat tmp
显示:1
ABCDEFG
无缓冲
无缓冲就是不进行缓冲,直接执行 I/O 操作。测试代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const char *string = "ABCDEFG";
int main() {
char buff[1024];
FILE *fp = NULL;
fp = fopen(FILEPATH, "w+");
if(fp == NULL) {
perror("fopen() error!\n");
}
int ret = 0;
ret = setvbuf(fp, buff, _IONBF, 1024);
if(ret != 0) {
perror("setvbuf error!\n");
}
fprintf(fp, "%s", string);
while(1){
}
return 0;
}
相比 test2,上面的代码有两个改动:
const char *string = "ABCDEFG;
,与 test1 保持一致ret = setvbuf(fp, buff, _IONBF, 1024);
,修改为无缓冲模式
同样的思路,运行程序,在另一个终端中可以确认tmp
文件已创建,然后键入1
cat tmp
显示:1
ABCDEFG(account)$xxx
上面的(account)$xxx
是终端显示的用户名,因为没有\n
,所以会与终端的用户名显示在一行。
如何刷新缓冲区
明确各种缓冲模式的特点后,也要明白如何刷新缓冲区。
其实上面已经提到一种刷新缓冲区的方法了,在行缓冲模式下,只需要在向流输入一个\n
,就会刷新缓冲区。
而 C 标准库也提供了fflush
函数用来刷新缓冲区。
使用 test1 中的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const char *string = "ABCDEFG";
int main() {
char buff[1024];
FILE *fp = NULL;
fp = fopen(FILEPATH, "w+");
if(fp == NULL) {
perror("fopen() error!\n");
}
int ret = 0;
ret = setvbuf(fp, buff, _IOFBF, 1024);
if(ret != 0) {
perror("setvbuf error!\n");
}
fprintf(fp, "%s", string);
fflush(fp);
while(1){
}
return 0;
}
相比 test1,上面的代码有个改动:
fflush(fp);
,手动刷新缓冲区
同样的思路,运行程序,在另一个终端中可以确认tmp
文件已创建,然后键入1
cat tmp
显示:1
ABCDEFG(account)$xxx
显示结果与 test3 一致。
调用fflush
函数后就会刷新缓冲区,任何一种缓冲模式下,都可以使用该函数来刷新缓冲区。
结语
C 语言中有关缓冲区的知识暂时就记录到这里了,如前所说,“缓冲”更多是一种思想,从这个角度去理解,应该能收获更多东西。