当前位置: 首页 > article >正文

Linux 简单模拟实现C语言文件流

🌇前言

在 C语言 的文件流中,存在一个 FILE 结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符 fd,在此类型之上,诞生了 C语言 文件相关操作,如 fopenfclosefwrite 等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的 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 1024typedef 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;
}

http://www.lryc.cn/news/2396075.html

相关文章:

  • ArcPy错误处理与调试技巧(3)
  • 小程序使用npm包的方法
  • Asp.Net Core SignalR的协议协商问题
  • Rust 学习笔记:发布一个 crate 到 crates.io
  • 剪枝中的 `break` 与 `return` 区别详解
  • Spring Boot 4.0实战:构建高并发电商系统
  • Vert.x学习笔记-EventLoop与Context的关系
  • 2025030给荣品PRO-RK3566开发板单独升级Android13的boot.img
  • 由enctype-引出post与get的关系,最后深究至请求/响应报文
  • 排序算法衍生问题
  • Mac电脑上本地安装 redis并配置开启自启完整流程
  • STP(生成树协议)原理与配置
  • 搭建基于VsCode的ESP32的开发环境教程
  • 【MFC】初识MFC
  • C++.二分法教程
  • 如何通过数据分析优化项目决策
  • 2024年数维杯国际大学生数学建模挑战赛B题空间变量协同估计方法研究解题全过程论文及程序
  • leetcode hot100刷题日记——34.将有序数组转换为二叉搜索树
  • thinkphp 5.1 部分知识记录<一>
  • RAG:面向知识密集型自然语言处理任务的检索增强生成
  • MVVM、MVC的区别、什么是MVVM
  • 网页自动化部署(webhook方法)
  • 线性代数入门:轻松理解二阶与三阶行列式的定义与理解
  • AU6825集成音频DSP的2x32W数字型ClaSSD音频功率放大器(替代TAS5825)
  • 华为云Flexus+DeepSeek征文|DeepSeek-V3/R1商用服务体验全流程
  • Go语言的原子操作
  • Visual Studio 2022 插件推荐
  • 【深度学习-pytorch篇】3. 优化器实现:momentum,NAG,AdaGrad,RMSProp,Adam
  • C# NX二次开发-查找连续倒圆角面
  • 今天遇到的bug