1.为什么要有缓冲区
缓冲区分成语言层面的缓冲区和操作系统层面的缓冲区
先说结论,语言的缓冲区可以减少系统调用的次数进而提高向文件写入和读取的效率。
2.举例子
向屏幕打印,无非就是向屏幕这个文件的缓冲区写入,然后在由操作系统刷新到显示器文件,这样显示器就可以显示内容了。
#include<unistd.h>2 #include<sys/stat.h>3 #include<sys/types.h>4 #include<fcntl.h>5 6 int main()7 {8 char s[] = "hello buffer\n";9 write(1,s,sizeof(s)); //显示器文件默认打开,fd = 110 return 0;11 }
直接调用系统调用,写到显示器文件的缓冲区,然后由操作系统刷新。
3.c语言缓冲区
将文件的缓冲区写入这件事,一定是由系统调用接口做的,而系统调用是有消耗的,所以在调用printf时,会将数据写入,c语言本身的缓冲区,然后根据刷新策略,由系统调用,将c语言缓冲的内容,刷新到文件的缓冲区,再由操作系统,将文件缓冲区刷新到文件中。
这就好比我想申请100字节的空间,一次malloc(100)肯定会比,10次mallc(10)更高效。
c语言各种文件接口一定是对系统调用的封装,因为不仅仅要引入缓冲区,还要保证不同平台之间的移植性。
那么c语言的缓冲区在哪里呢?
我们对比一下,系统调用和c语言的接口就能推断出
write接口是通过fd,去文件描述表,中寻找对应的文件的缓冲区写入。
fwrite却是向FILE *stream这个流中写入,说明FILE*stream这个结构体,一定封装了,fd和一个缓冲区,因为fwrite是write的封装。
#include<unistd.h>2 #include<sys/stat.h>3 #include<sys/types.h>4 #include<fcntl.h>5 #include<stdio.h>6 #include<string.h>7 8 int main()9 {10 //使用系统调用接口11 const char *s1 = "hello write";12 write(1,s1,strlen(s1));13 //使用c语言接口14 const char *s2 ="hello fwirte";15 fwrite(s2,strlen(s2),1,stdout);16 const char *s3 = "hello printf"; 17 printf("%s",s3);18 19 fork();20 return 0;21 }
运行结果
我们发现系统的接口打印了一次,而c语言接口打印了两次。
因为向显示器打印,刷新策略默认是行刷新,只有在遇到\n时才会将c语言缓冲区内容,刷新到显示器文件的缓冲区。 但是因为字符串都没有换行,所以hello fwrite和hello printf都是在c语言的缓冲区之内,fork创建子进程,接着进程要退出了,要把c语言缓冲区的内容,全部刷新到文件的缓冲区,因为创建进程后使用数据会发生写时拷贝,这样父进程缓冲区有一份hello fwrite和hello printf,子进程有一份hello fwrite和hello printf,所以c语言接口会被打印两次。
系统接口直接刷新到文件缓冲区,自然没有以上问题。
我们只要在每个字符串后面加上个\n就会每个都输出一次,因为行刷新遇见\n就会刷新到文件缓冲区,最后fork()的时候,c语言的缓冲区是空的不会发生写时拷贝,自然不会打印两次。