流水潺潺:探寻Linux下C语言文件流的诗意实现
一、前言文件流的哲学在C语言中文件流是一种抽象的概念它将复杂的文件操作简化为一系列流畅的读写动作。标准库中的FILE结构体及其相关函数如fopen()、fread()、fwrite()等为程序员提供了一个优雅的接口使我们能够专注于数据的处理而非底层的细节。然而这些优雅的接口背后隐藏着怎样的奥秘今天我们将揭开这层面纱亲手构建一个简化版的文件流系统。在这里插入图片描述本文重点 模拟实现FILE及C语言文件操作相关函数注意 本文实现的只是一个简单的 demo重点在于理解系统调用及缓冲区️正文一、FILE 结构设计在设计 FILE 结构体前首先要清楚 FILE 中有自己的缓冲区及冲刷方式在这里插入图片描述缓冲区的大小和刷新方式因平台而异这里我们将 大小设置为1024刷新方式选择行缓冲为了方便对缓冲区进行控制还需要一个下标_current当然还有 最重要的文件描述符_fd代码语言javascriptAI代码解释#define BUFFER_SIZE 1024 //缓冲区大小 //通过位图的方式控制刷新方式 #define BUFFER_NONE 0x1 //无缓冲 #define BUFFER_LINE 0x2 //行缓冲 #define BUFFER_ALL 0x4 //全缓冲 typedef struct MY_FILE { char _buffer[BUFFER_SIZE]; //缓冲区 size_t _current; //缓冲区下标 int _flush; //刷新方式位图结构 int _fd; //文件描述符 }MY_FILE;当前模拟实现的FILE只具备最基本的功能重点在于呈现原理在模拟实现C语言文件操作相关函数前需要先来简单回顾下二、函数使用及分析主要实现的函数有以下几个fopen 打开文件fclose 关闭文件fflush 进行缓冲区刷新fwrite 对文件中写入数据fread 读取文件数据代码语言javascriptAI代码解释#include stdio.h #include assert.h #include string.h int main() { //打开文件写入数据 FILE* fp fopen(file.txt, w); assert(fp); const char* str 露易斯湖三面环山层峦叠嶂翠绿静谧的湖泊在宏伟山峰及壮观的维多利亚冰川的映照下更加秀丽迷人; char buff[1024] { 0 }; snprintf(buff, sizeof(buff), str); fwrite(buff, 1, sizeof(buff), fp); fclose(fp); return 0; }在这里插入图片描述代码语言javascriptAI代码解释#include stdio.h #include assert.h #include string.h int main() { //打开文件并从文件中读取信息 FILE* fp fopen(file.txt, r); assert(fp); char buff[1024] { 0 }; int n fread(buff, 1, sizeof(buff) - 1, fp); buff[n] \0; printf(%s, buff); fclose(fp); return 0; }fopen打开指定文件可以以多种方式打开若是以读方式打开时文件不存在会报错fclose根据 FILE* 关闭指定文件不能重复关闭fwrite对文件中写入指定数据一般是借助缓冲区进行写入fread读取文件数据同理一般是借助缓冲区先进行读取不同的缓冲区有不同的刷新策略如果未触发相应的刷新策略会导致数据滞留在缓冲区中比如如果内存中的数据还没有刷新就断电的话会导致数据丢失除了通过特定方式进行缓冲区冲刷外还可以手动刷新缓冲区在C语言中手动刷新缓冲区的函数为fflush代码语言javascriptAI代码解释#include stdio.h #include unistd.h int main() { int cnt 20; while(cnt) { printf(he); //故意不触发缓冲 cnt--; if(cnt % 10 5) { fflush(stdout); //刷新缓冲区 printf(\n当前已冲刷cnt: %d\n, cnt); } sleep(1); } return 0; }在这里插入图片描述在cnt15和5时先手动冲刷两次之后程序结束自动全部冲刷总的来说这些文件操作相关函数都是在对缓冲区进行写入及冲刷将数据拷贝给内核缓冲区再由内核缓冲区刷给文件2.1、文件打开 fopen代码语言javascriptAI代码解释MY_FILE *my_fopen(const char *path, const char *mode); //打开文件打开文件分为以下几步根据传入的mode确认打开方式通过系统接口open打开文件创建 MY_FILE 结构体初始化内容返回创建好的 MY_FILE 类型因为打开文件存在多种失败情况权限不对/open 失败/malloc 失败等所以当打开文件失败后需要返回NULL注意 假设是因 malloc 失败的那么在返回之前需要先关闭 fd否则会造成资源浪费// 打开文件代码语言javascriptAI代码解释// 打开文件 MY_FILE *my_fopen(const char *path, const char *mode) { assert(path mode); // 确定打开方式 int flags 0; // 打开方式 // 读O_RDONLY 读O_RDONLY | O_WRONLY // 写O_WRONLY | O_CREAT | O_TRUNC 写O_WRONLY | O_CREAT | O_TRUNC | O_RDONLY // 追加 O_WRONLY | O_CREAT | O_APPEND 追加O_WRONLY | O_CREAT | O_APPEND | O_RDONLY // 注意不考虑 b 二进制读写的情况 if (*mode r) { flags | O_RDONLY; if (strcmp(r, mode) 0) flags | O_WRONLY; } else if (*mode w || *mode a) { flags | (O_WRONLY | O_CREAT); if (*mode w) flags | O_TRUNC; else flags | O_APPEND; if (strcmp(w, mode) 0 || strcmp(a, mode) 0) flags | O_RDONLY; } else { // 无效打开方式 assert(false); } // 根据打开方式打开文件 // 注意新建文件需要设置权限 int fd 0; if (flags O_CREAT) fd open(path, flags, 0666); else fd open(path, flags); if (fd -1) { // 打开失败的情况 return NULL; } // 打开成功了创建 MY_FILE 结构体并返回 MY_FILE *new_file (MY_FILE *)malloc(sizeof(MY_FILE)); if (new_file NULL) { // 此处不能断言需要返回空 close(fd); // 需要先把 fd 关闭 perror(malloc FILE fail!); return NULL; } // 初始化 MY_FILE memset(new_file-_buffer, \0, BUFFER_SIZE); // 初始化缓冲区 new_file-_current 0; // 下标置0 new_file-_flush BUFFER_LINE; // 行刷新 new_file-_fd fd; // 设置文件描述符 return new_file; }2.2、文件关闭 fclose代码语言javascriptAI代码解释int my_fclose(MY_FILE *fp); //关闭文件文件在关闭前需要先将缓冲区中的内容进行冲刷否则会造成数据丢失注意my_fclose返回值与close一致因此可以复用代码语言javascriptAI代码解释// 关闭文件 int my_fclose(MY_FILE *fp) { assert(fp); // 刷新残余数据 if (fp-_current 0) my_fflush(fp); // 关闭 fd int ret close(fp-_fd); // 释放已开辟的空间 free(fp); fp NULL; return ret; }2.3、缓冲区刷新 fflush代码语言javascriptAI代码解释int my_fflush(MY_FILE *stream); //缓冲区刷新缓冲区冲刷是一个十分重要的动作它决定着 IO 是否正确这里的my_fflush是将用户级缓冲区中的数据冲刷至内核级缓冲区冲刷的本质拷贝用户先将数据拷贝给用户层面的缓冲区再系统调用将用户级缓冲区拷贝给内核级缓冲区最后才将数据由内核级缓冲区拷贝给文件因此IO是非常影响效率的。数据传输过程必须遵循冯诺依曼体系结构函数fsync将内核中的数据手动拷贝给目标文件内核级缓冲区的刷新策略极为复杂为了确保数据能正常传输可以选择手动刷新注意 在冲刷完用户级缓冲区后write需要将缓冲区清空否则缓冲区就一直满载了代码语言javascriptAI代码解释// 缓冲区刷新 int my_fflush(MY_FILE *stream) { assert(stream); // 将数据写给文件 int ret write(stream-_fd, stream-_buffer, stream-_current); stream-_current 0; // 每次刷新后都需要清空缓冲区 fsync(stream-_fd); // 将内核中的数据强制刷给磁盘(文件) if (ret ! -1) return 0; else return -1; }2.3、数据写入 fwrite代码语言javascriptAI代码解释size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream); //数据写入数据写入用户级缓冲区的步骤判断当前用户级缓冲区是否满载如果满了需要先刷新再进行后续操作获取当前待写入的数据大小user_size及用户级缓冲区剩余大小my_size方便进行后续操作如果my_size user_size说明缓冲区容量足够直接进行拷贝否则说明缓冲区容量不足需要重复冲刷-拷贝-再冲刷的过程直到将数据全部拷贝拷贝完成后需要判断是否触发相应的刷新策略比如行刷新-最后一个字符是否为 \n如果满足条件就刷新缓冲区数据写入完成返回实际写入的字节数简化版即user_size如果是一次写不完的情况需要通过循环写入数据并且在缓冲区满后进行刷新因为循环写入时目标数据的读取位置是在不断变化的一次读取一部分不断后移所以需要对读取位置和读取大小进行特殊处理2.4、数据读取 fread在进行数据读取时需要经历文件-内核级缓冲区-用户级缓冲区-目标空间的繁琐过程并且还要考虑用户级缓冲区是否能够一次读取完所有数据若不能则需要多次读取注意读取前如果用户级缓冲区中有数据的话需要先将数据刷新给文件方便后续进行操作读取与写入不同读取结束后需要考虑 \0 的问题在最后一个位置加如果不加的话会导致识别错误系统(内核)不需要 \0但C语言中的字符串结尾必须加 \0现在是 系统-用户C语言代码语言javascriptAI代码解释// 数据读取 size_t my_fread(void *ptr, size_t size, size_t nmemb, MY_FILE *stream) { // 数据读取前需要先把缓冲区刷新 if (stream-_current 0) my_fflush(stream); size_t user_size size * nmemb; size_t my_size BUFFER_SIZE; // 先将数据读取到FILE缓冲区中再赋给 ptr if (my_size user_size) { // 此时缓冲区中足够存储用户需要的所有数据只需要读取一次 read(stream-_fd, stream-_buffer, my_size); memcpy(ptr, stream-_buffer, my_size); *((char *)ptr my_size - 1) \0; } else { int ret 1; size_t tmp user_size; while (ret) { // 一次读不完需要多读取几次 ret read(stream-_fd, stream-_buffer, my_size); stream-_buffer[ret] \0; memcpy(ptr (tmp - user_size), stream-_buffer, my_size); stream-_current 0; user_size - my_size; } } size_t readn strlen(ptr); return readn; }2.6 小结用户在进行文件流操作时实际要进行至少三次的拷贝用户-用户级缓冲区-内核级缓冲区-文件C语言 中众多文件流操作都是在完成用户-用户级缓冲区的这一次拷贝动作其他语言也是如此最终都是通过系统调用将数据冲刷到磁盘文件中在这里插入图片描述最后再简单提一下printf和scanf的工作原理无论是什么类型最终都要转为字符型进行存储程序中的各种类型只是为了更好的解决问题printf根据格式读取数据如整型、浮点型并将其转为字符串定义缓冲区然后将字符串写入缓冲区stdout最后结合一定的刷新策略将数据进行冲刷scanf读取数据至缓冲区stdin根据格式将字符串扫描分割存入字符指针数组最后将字符串转为对应的类型赋值给相应的变量这也就解释了为什么要确保输出/输入格式与数据匹配如果不匹配的话会导致读取/赋值错误本篇关于文件操作模拟实现的介绍就暂告段落啦希望能对大家的学习产生帮助欢迎各位佬前来支持斧正

相关新闻

Flutter 三方库 kdtree 的鸿蒙化适配指南 - 掌控空间搜索资产、精密算法治理实战、鸿蒙级算力专家

Flutter 三方库 kdtree 的鸿蒙化适配指南 - 掌控空间搜索资产、精密算法治理实战、鸿蒙级算力专家

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net Flutter 三方库 kdtree 的鸿蒙化适配指南 - 掌控空间搜索资产、精密算法治理实战、鸿蒙级算力专家 在鸿蒙跨平台应用执行高级空间数据检索与多维 K-D 树算法资产指控(如构建一个…

2026/5/17 9:32:11 阅读更多 →
前后端分离医院病历管理系统系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程

前后端分离医院病历管理系统系统|SpringBoot+Vue+MyBatis+MySQL完整源码+部署教程

摘要 随着医疗信息化的快速发展,传统医院病历管理系统在数据交互效率、系统扩展性和用户体验方面逐渐暴露出局限性。纸质病历管理方式难以满足现代医院对高效、安全和便捷的数据处理需求,亟需一种新型的电子病历管理系统。基于前后端分离架构的设计理念&…

2026/5/17 9:32:07 阅读更多 →
Spring Cloud Data Flow 简介

Spring Cloud Data Flow 简介

Spring Cloud Data Flow 介绍 1.Data flow 是一个用于开发和执行大范围数据处理其模式包括ETL,批量运算和持续运算的统一编程模型和托管服务。 2.对于在现代运行环境中可组合的微服务程序来说,spring cloud data flow是一个原生云可编配的服务。 使用s…

2026/7/3 18:39:49 阅读更多 →

最新新闻

Startup AI自动化落地实战:客服、库存与决策的闭环打法

Startup AI自动化落地实战:客服、库存与决策的闭环打法

1. 项目概述:当AI自动化真正落地到 startup 的日常毛细血管里 我带过三支不同阶段的创业团队,从十几人的 SaaS 工具公司,到二十人出头的跨境 DTC 品牌,再到刚完成种子轮的工业 IoT 解决方案团队。过去三年里,我亲手拆过…

2026/7/4 10:13:45 阅读更多 →
ID3到XGBoost:决策树模型演进的工程实战路径

ID3到XGBoost:决策树模型演进的工程实战路径

1. 这不是“树”的科普,而是决策模型演进的实战路线图 你打开任何一本机器学习入门书,十有八九会在第三章遇到“决策树”——画着几根分叉的流程图,讲着信息增益、基尼不纯度这些词,然后戛然而止。但真实项目里,没人只…

2026/7/4 10:13:45 阅读更多 →
十项重塑产业的AI工程突破:从因果推理到边缘大模型

十项重塑产业的AI工程突破:从因果推理到边缘大模型

1. 项目概述:这不是一份“AI新闻简报”,而是一份从业者手写的“技术影响地图”“10 Game-changing AI Breakthroughs Worth Knowing About”——这个标题乍看像科技媒体的年度盘点,但如果你真把它当普通资讯扫一眼就划走,那你就错…

2026/7/4 10:13:45 阅读更多 →
科研信息熵压缩:月度4篇论文精读方法论

科研信息熵压缩:月度4篇论文精读方法论

1. 项目概述:这不是一份文献综述,而是一份科研节奏校准器 “Month in 4 Papers (January 2025)”——这个标题乍看像一份学术期刊的月度简报,但如果你在高校实验室熬过通宵、在工业界赶过模型上线 deadline、或是在读博第三年反复修改 propo…

2026/7/4 10:09:45 阅读更多 →
游戏陪玩App的XSS防御实战:从原理到纵深防护体系构建

游戏陪玩App的XSS防御实战:从原理到纵深防护体系构建

1. 项目概述:为什么游戏陪玩App必须严防XSS?最近在跟一个做游戏陪玩平台的朋友聊技术债,他提到一个让我后背发凉的问题:他们平台上线没多久,就发现有用户在陪玩师的个人简介里,嵌入了能自动跳转到钓鱼网站的…

2026/7/4 10:09:45 阅读更多 →
从零实现大语言模型:Happy-LLM开源教程带你掌握Transformer与微调实战

从零实现大语言模型:Happy-LLM开源教程带你掌握Transformer与微调实战

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Claude 随心用,限时 5 折。 👉 点击领海量免费额度 最近在社区里看到很多朋友对 AI 大模型开发跃跃欲试,但往往被海量的论文、复杂的数学公式和动辄几十个 G 的模型权重劝退…

2026/7/4 10:09:45 阅读更多 →

日新闻

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 发布:关键安全修复版本,多项问题得到解决

Memcached 1.6.43 正式发布,这是一个关键的安全修复版本,修复了多个方面的问题,还对部分功能进行了优化。 安全修复亮点 此次发布在安全修复上表现突出。binprot 避免了项目引用计数溢出,mcmc 因安全问题提升了上游版本号&#xf…

2026/7/4 0:04:29 阅读更多 →
终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案

终极指南:使用HMCL启动器跨平台畅玩Minecraft的完整解决方案 【免费下载链接】HMCL A Minecraft Launcher which is multi-functional, cross-platform and popular 项目地址: https://gitcode.com/gh_mirrors/hm/HMCL HMCL(Hello Minecraft! Lau…

2026/7/4 0:06:29 阅读更多 →
KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

KMX63与PIC18F66K40在嵌入式HMI中的硬件协同与低功耗设计

1. KMX63与PIC18F66K40的硬件协同架构解析KMX63作为一款三轴加速度计和磁力计组合传感器,与PIC18F66K40微控制器的搭配堪称嵌入式HMI开发的黄金组合。这套硬件组合的核心优势在于KMX63提供的高精度运动感知能力与PIC18F66K40强大的信号处理能力形成了完美互补。KMX6…

2026/7/4 0:06:29 阅读更多 →

周新闻

月新闻