目录
[头文件 mystdio.h](#头文件 mystdio.h)
[源文件 mystdio.c](#源文件 mystdio.c)
缓冲区
什么是缓冲区?
缓冲区(Buffer)是指一块临时存储数据的内存区域。
-
临时存储 :缓冲区用于临时存储输入或输出的数据。例如,在读取文件时,可以将一部分数据先读入缓冲区,然后程序可以从缓冲区中快速获取数据,而不需要每次都直接访问磁盘。
-
提高效率 :由于磁盘I/O操作比内存操作慢得多,使用缓冲区可以减少对较慢设备的直接访问次数,从而加快数据处理的速度。同样,对于网络通信,缓冲区可以帮助平滑数据流,避免因网络延迟而导致的性能问题。
-
批量处理 :缓冲区允许一次性处理多份数据 ,而不是每次只处理一份。这减少了系统调用的开销,提高了整体性能。
C语言的缓冲区
我们一直说的缓冲区,其实是语言层面的缓冲区,和操作系统是没有直接关系的,即C语言会自带缓冲区!
当我们进行写入操作时(向显示器写入或者文件写入),系统调用接口为 write,在C语言中用的是 printf/fputs/fwrite(在底层中封装了系统调用),而调用系统调用接口是有时间和空间的成本(宁愿让它一次性拷贝多个数据,也不要让它多次拷贝),所以C语言在语言层面设置了一个缓冲区,一般在C语言写数据时,是先把数据写到C语言的缓冲区中,C语言再定期把数据交给系统调用接口,写入到操作系统中,以减少系统调用的次数。
缓冲机制:
全缓冲 :在这种模式下,当缓冲区被填满或者显式地刷新缓冲区时,数据才会被写入到实际的输出设备。文件通常是全缓冲的,这意味着当你使用
fprintf()
向文件写入数据时,数据会先存放在缓冲区里,直到缓冲区满了或者是你关闭了文件(调用fclose()
),或者是你手动刷新缓冲区(调用fflush()
)。
行缓冲 :对于一些特定的流,比如标准输入(stdin)和标准输出(stdout),默认情况下是行缓冲的。这表示每当遇到换行符('\n')或者缓冲区满的时候,数据就会被自动刷新到终端。如果你使用printf()
打印信息,那么除非你打印了一个换行符或者缓冲区已满,否则数据可能不会立即出现在屏幕上。
无缓冲 :也称为不带缓冲。这种模式下,每个输入或输出请求都会立即执行,数据会立刻被发送到目的地或者从源头接收过来。标准错误流(stderr)默认是无缓冲的,因为错误信息通常需要尽快显示出来以便用户能够及时响应。
如何验证C语言缓冲区的存在?
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
int main()
{
//调用系统调用,向 stdout 文件写入
const char *s1="hello write\n";
write(1,s1,strlen(s1));
//调用C语言接口
const char *s2="hello fprintf\n";
fprintf(stdout,"%s",s2);
//对照
const char *s3="hello fwrite\n";
fwrite(s3,strlen(s3),1,stdout);
fork();//创建子进程
return 0;
}
同一份代码重定向到不同的位置输出不同的内容!
- 向显示器打印无论有没有fork都是行刷新,都是3条;
- 重定向后,变成全缓冲,我们写的 "hello fprintf" "hello fwrite"都先放到了C语言的缓冲区,fork 创建出的子进程和父进程都指向了同一个C语言缓冲区,子进程退出后,就会刷新C语言缓冲区,因为进程具有独立性,所以这时候子进程会进行写时拷贝,把C语言缓冲区的数据拷贝了一份,接着父进程退出,也会刷新C语言缓冲区的数据,所以同一句话就被刷新了2次,而系统调用并没有把**"hello write"写入到C语言的缓冲区中,而是直接写入到操作系统**,和进程没关系,与写时拷贝无关,所以最终打印出了5条
模拟缓冲区的代码
头文件 mystdio.h
cpp
#pragma once
#include<stdio.h>
#define SIZE 4096
//选项1,无刷新
#define NONE_FLUSH (1<<1)
//选项2,行刷新
#define LINE_FLUSH (1<<2)
//选项3,全刷新
#define FULL_FLUSH (1<<3)
typedef struct _myFILE
{
char outbuffer[SIZE];//缓冲区
int pos;//要写入的位置
int cap;//缓冲区的顶
int fileno;//文件描述符
int flush_mode;//刷新模式
}myFILE;
myFILE *my_fopen(const char *pathname,const char *mode);
int my_fwrite(myFILE *fp,const char *s,int size);
void my_fflush(myFILE *fp);
void my_fclose(myFILE *fp);
void DebugPrint(myFILE *fp);
源文件 mystdio.c
cpp
#include"mystdio.h"
#include<string.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
//打开文件,及选择读写方式
myFILE *my_fopen(const char *pathname,const char *mode)
{
int flag=0;
//r模式(读)
if(strcmp(mode,'r')==0)
{
flag|=O_RDONLY;
}
//w模式(写)
else if(strcmp(mode,'w')==0)
{
//文件不存在就新建文件,存在的话就不用新建
//每次打开该文件时清空文件内容
flag|=(O_CREAT|O_WRONLY|O_TRUNC);
}
//a模式(追加)
else if(strcmp(mode,'a')==0)
{
//文件不存在就新建文件,存在的话就不用新建
//不会清空文件内容
flag|=(O_CREAT|O_WRONLY|O_APPEND);
}
else return NULL;//不存在的模式
//用系统调用接口,获取文件描述符
int fd=0;//文件描述符
if(flag & O_WRONLY)
{
umask(0);//设置文件权限
fd=open(pathname,flag,0666);
}
else{
fd=open(pathname,flag);
}
//文件创建失败
if(fd<0) return NULL;
myFILE *fp=(myFILE*)malloc(sizeof(myFILE));
if(fp==NULL) return NULL;//开辟空间失败
fp->cap=SIZE;
fp->fileno=fd;
fp->pos=0;
fp->flush_mode=LINE_FLUSH;
}
//刷新缓冲区
void my_fflush(myFILE *fp)
{
if(fp->pos==0) return;//没必要调用系统调用接口
write(fp->fileno,fp->outbuffer,fp->pos);
fp->pos=0;//清空缓冲区
}
//向文件写入
int my_fwrite(myFILE *fp,const char *s,int size)
{
memcpy(fp->outbuffer+fp->pos,s,size);
fp->pos+=size;
//判断是否需要刷新
//行刷新且结尾为 \n
if((fp->flush_mode & LINE_FLUSH) && fp->outbuffer[fp->pos-1]=='\n')
{
my_fflush(fp);
}
//全刷新
else if((fp->flush_mode & FULL_FLUSH) && fp->pos==fp->cap)
{
my_fflush(fp);
}
return size;
}
//关闭文件
void my_fclose(myFILE *fp)
{
//刷新缓冲区
my_fflush(fp);
close(fp->fileno);
free(fp);//释放空间
}
//把刷新模式用字符表示
const char *toString(int flag)
{
if(flag & NONE_FLUSH) return "NONE";
else if(flag & LINE_FLUSH) return "LINE";
else if(flag & FULL_FLUSH) return "FULL";
else return "UNKNOW";
}
//打印错误信息
void DebugPrint(myFILE *fp)
{
printf("outbuffer:%s\n",fp->outbuffer);
printf("pos:%d\n",fp->pos);
printf("cap:%d\n",fp->cap);
printf("fd:%d\n",fp->fileno);
printf("flush_mode:%s\n",toString(fp->flush_mode));
}
代码区分全缓冲和行缓冲:
全缓冲:
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include"mystdio.h"
const char *filename="./log.txt";
int main()
{
myFILE *fp=my_fopen(filename,"w");
if(fp==NULL) return 1;//打开失败
int cnt=5;
char buffer[64];
while(cnt)
{
snprintf(buffer,sizeof(buffer),"helloworld,hellobit,%d ",cnt--);
my_fwrite(fp,buffer,strlen(buffer));
DebugPrint(fp);
sleep(2);//休眠2s,看清现象
}
my_fclose(fp);
return 0;
}
行缓冲:
行缓冲是在打印 helloworld,hellobit 的基础上加上**\n**。
cpp
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include"mystdio.h"
const char *filename="./log.txt";
int main()
{
myFILE *fp=my_fopen(filename,"w");
if(fp==NULL) return 1;//打开失败
int cnt=5;
char buffer[64];
while(cnt)
{
snprintf(buffer,sizeof(buffer),"helloworld,hellobit,%d\n",cnt--);
my_fwrite(fp,buffer,strlen(buffer));
DebugPrint(fp);
sleep(2);//休眠2s,看清现象
}
my_fclose(fp);
return 0;
}
对比之下就可以看出,全缓冲的缓冲区会在 while 结束后才刷新缓冲区,而行缓冲每写一句刷新一次缓冲区。