🌇前言
在
C语言
的文件流中,存在一个FILE
结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符fd
,在此类型之上,诞生了C语言
文件相关操作,如fopen
、fclose
、fwrite
等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的C语言
文件流。
注意: 本文实现的只是一个简单的 demo
,重点在于理解系统调用及缓冲区
🏙️正文
1、FILE 结构设计
在设计 FILE
结构体前,首先要清楚 FILE
中有自己的缓冲区及冲刷方式
#pragma once
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<stdio.h>
#define flush_no 1 //无缓冲
#define flush_line 2 //行缓存
#define flush_all 4 //满了在缓冲
#define SIZE 1024
typedef struct IO_FILE
{
int fileno;
int _flush;
char inbuffer[SIZE];
char outbuffer[SIZE];
int out_pos;
}_FILE;
void _fseek(_FILE*stream,int num,int f);
_FILE* _fopen(const char*filename,const char*flag);
int _fwrite (_FILE*fp,const char*s ,int len);
void _fclose(_FILE* fp);
void _fflush(_FILE* fp);
size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream );
2、函数使用及分析
主要实现的函数有以下几个:
fopen
打开文件fclose
关闭文件fflush
进行缓冲区刷新fwrite
对文件中写入数据fread
读取文件数据
实现的大致思路:
C语言文件流的过程。
FILE 结构体封装了用户级缓冲区(输入,输出)和文件指针,指向下一个读写的位置,还有文件标识符,等等。而fopen 是对open的封装
通过传入的读写方式打开对应的文件,在创建一个对应的结构体,初始化对应结构体的文件标识符,刷新方式。fwrite是对write的封装,它先是将要写入的数据写道FILE结构体封装的缓冲区里面,在判断是是否要刷新,如果要刷新就通过write写到对应的文件之中。fread封装的是read,它先是通过read将数据从文件中读到用户级缓冲区,然后在利用memcpy拷贝到目标数组中,这样可以减少拷贝次数。当fopen打开时采用“w+”的方式时,如果先向一个文件中写入一段数据,在调用fread读取的时候会发生问题,因为再写入时文件的指针在末尾,此时如果在去读的话,文件指针后面没有数据了,就会造成读取失败。在C语言中用户需要手动调用rewind() 或 fseek()函数将实现指针移动。fseek()是将lseek()函数进行封装,我们创建一个_fseek函数将系统接口 lseek()函数封装其中,将文件指针移动到文件的开始,也可以解决这个问题了。fclose在底层调用了close。在程序结束之前要对用户及缓冲区进行检查是否为空,不为空进行刷新。然后才可以将文件关闭。fflush函数封装的是write,它将缓冲区的数据写入文件中去。就这样我们可以实现一个简单的C语言文件流。实际上write写入应该写入到内核级缓冲区,但是我们无法实现这个,而且fflush可以对内核缓冲区进行刷新。
代码:
#include"mystdio.h"
void _fseek(_FILE*stream,int num,int f)
{
lseek(stream->fileno,num,f);
}
_FILE* _fopen(const char* filename,const char* flag)
{
assert(filename&&flag);//不能传空串
int fd=0;
int f=0;//打开方式
if(strcmp(flag,"r")==0)//读
{
f=O_RDONLY;//系统级打开的参数
fd=open(filename,f,0666);
}
else if(strcmp(flag,"r+")==0)//读写
{
f=(O_RDONLY|O_WRONLY);
fd=open(filename,f,0666);
}
else if(strcmp(flag,"w")==0)//两者相等返回0
{
f= (O_CREAT|O_WRONLY|O_TRUNC);
fd=open(filename,f,0666);
}
else if(strcmp(flag,"w+")==0)//读写
{
//printf("w+\n");
f=(O_RDWR | O_CREAT | O_TRUNC);
fd=open(filename,f,0666);
}
else if(strcmp(flag,"a")==0)//追加写
{
f=(O_CREAT|O_WRONLY|O_APPEND);
fd=open(filename,f,0666);
}
else if(strcmp(flag,"a+")==0)//追加读写
{
f=(O_CREAT|O_WRONLY|O_APPEND|O_RDONLY);
fd=open(filename,f,0666);
}
else if(strcmp(flag,"r")==0)//只读
{
f=O_RDONLY;
fd=open(filename,f);
}
else return NULL;
if(fd==-1) return NULL;
_FILE* fdd=(_FILE*)malloc(sizeof(_FILE));
if(!fdd)//创建失败
{
perror("eror:\n");
exit(-1);
}
memset(fdd, 0, sizeof(_FILE));//初始化用户及缓冲区
fdd->fileno=fd;
//printf("fdd->fileno: %d\n", fdd->fileno)
fdd->_flush=flush_line;
fdd->out_pos=0;
//printf("fdd->fileno: %d\n", fdd->fileno);
return fdd ;
}
int _fwrite(_FILE*fp,const char* s,int len)
{
memcpy(&fp->outbuffer[fp->out_pos],s,len);//先写到用户及缓冲区,再看刷新方式
fp->out_pos+=len;
if(fp->_flush==flush_no)//不缓存
{
write(fp->fileno,fp->outbuffer,fp->out_pos);
fp->out_pos=0;
}
else if(fp->_flush==flush_line)//行缓存 abcd\n 5
{
if(s[len-1]=='\n')
{
// printf("行\n");
write(fp->fileno,fp->outbuffer,fp->out_pos);
fp->out_pos=0;
}
}
else{//全缓冲,满了才刷到文件中
if(fp->out_pos==SIZE)
{
write(fp->fileno,fp->outbuffer,fp->out_pos);
fp->out_pos=0;
}
}
return len;
}
void _fclose(_FILE* fp)
{
assert(fp);
if(fp->out_pos>0) _fflush(fp);//关闭之前先看用户及缓冲区
close(fp->fileno);
free(fp);
}
void _fflush(_FILE* fp)//可以刷新内核级缓冲区
{
if(fp->out_pos>0)
{
write(fp->fileno,fp->outbuffer,fp->out_pos);//刷新就是写入
fp->out_pos=0;
//lseek(fp->fileno, 0, SEEK_SET);
}
}
size_t _fread(void*ptr,size_t size,size_t num, _FILE*stream)
{
if(stream->out_pos>0) _fflush(stream);
size_t readsize= size*num;//ptr的大小
size_t buffersize=SIZE;//缓冲区的大小
size_t total=0;
if(buffersize>=readsize)
{
ssize_t ret=read(stream->fileno,stream->inbuffer,readsize);
if(ret<0)
{
printf("不对\n");
perror("ret\n");
}
memcpy(ptr,stream->inbuffer,ret);
*((char*)ptr+ret)='\0';
total=ret;
//printf("%s\n",ptr);
}
else
{
while(total<readsize)
{
size_t ret=read(stream->fileno,stream->inbuffer,buffersize);
if(ret<0) break;
memcpy((char*)ptr+total,stream->inbuffer,ret);
total+=ret;
*((char*)ptr+total)='\0';
}
}
return total;
}
main.c
#include "mystdio.h"
#include<stdio.h>
int main()
{
_FILE* fp=_fopen("log.txt","w+");
//printf("file: %d", fp->fileno);
const char* str="hello,world\n";
_fwrite(fp,str,strlen(str));
_fseek(fp,0,SEEK_SET);
char ptr[10]="";
_fread(ptr,1,5,fp);
printf("%s\n",ptr);
_fclose(fp);
return 0;
}