<Linux> 模拟实现文件流 - 简易版

目录

1. FILE 结构设计

2、函数使用及分析

3、文件打开 fopen

4. 缓冲区刷新fflush

5. 数据写入fwrite

6. 文件关闭 fclose

7. 测试

8. 小结


1. FILE 结构设计

在设计 FILE 结构体前,首先要清楚 FILE 中有自己的缓冲区及冲刷方式

缓冲区的大小和刷新方式因平台而异,这里我们将 大小设置为 1024 刷新方式选择 行缓冲,为了方便对缓冲区进行控制,还需要一个下标 _current,当然还有 最重要的文件描述符 _fd

#include <stdio.h>//通过位图的方式,控制刷新方式
#define BUFFER_NONE 0x1    //无缓冲
#define BUFFER_LINE 0x2    //行缓冲
#define BUFFER_ALL 0x4     //全缓冲
#define BUFFER_SIZE 1024   //缓冲区大小typedef struct MY_FILE
{char _buffer[BUFFER_SIZE];  //缓冲区size_t _current;            //缓冲区下标int _flush;                 //刷新方式,位图结构int _fd;                    //文件描述符
}MY_FILE;

2、函数使用及分析

主要实现的函数有以下几个:

  • fopen 打开文件
  • fclose 关闭文件
  • fflush 进行缓冲区刷新
  • fwrite 对文件中写入数据
  • fread 读取文件数据
#include <stdio.h>
#include <assert.h>
#include <string.h>#define SIZE 1024
#define FE "file.txt"int main()
{//打开文件,写入数据FILE* fp = fopen(FE, "w");assert(fp);const char* str = "good morning\n";char buff[SIZE] = { 0 };snprintf(buff, sizeof(buff), str);fwrite(buff, 1, sizeof(buff), fp);fclose(fp);return 0;
}

  • fopen   -- 打开指定文件,可以以多种方式打开,若是以读方式打开时,文件不存在会报错
  • fclose  -- 根据 FILE* 关闭指定文件,不能重复关闭
  • fwrite   --  对文件中写入指定数据,一般是借助缓冲区进行写入

不同的缓冲区有不同的刷新策略,如果未触发相应的刷新策略,会导致数据滞留在缓冲区中,比如如果内存中的数据还没有刷新就断电的话,会导致数据丢失;除了通过特定方式进行缓冲区冲刷外,还可以手动刷新缓冲区,在 C语言 中,手动刷新缓冲区的函数为 fflush

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;
}

先手动冲刷五次,再手动冲刷十次,最后程序结束后,自动冲刷剩余五次. 

总的来说,这些文件操作相关函数,都是在对缓冲区进行写入及冲刷,将数据拷贝给内核缓冲区,再由内核缓冲区刷给文件

3、文件打开 fopen

MY_FILE *my_fopen(const char *path, const char *mode); //打开文件

打开文件分为以下几步:

  • 根据传入的 mode 确认打开方式
  • 通过系统接口 open 打开文件
  • 创建 MY_FILE 结构体,初始化内容
  • 返回创建好的 MY_FILE 类型

因为打开文件存在多种失败情况:权限不对 / open 失败 / malloc 失败等,所以当打开文件失败后,需要返回 NULL

注意: 假设是因 malloc 失败的,那么在返回之前需要先关闭 fd,否则会造成资源浪费

// 读:   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 二进制读写的情况MY_FILE *my_fopen(const char *path, const char *mode)
{// 1. 识别打开方式int flag = 0; // 打开方式if(strcmp(mode, "r") == 0)       flag |= O_RDONLY;else if(strcmp(mode, "r+") == 0) flag |= (O_RDONLY | O_WRONLY);else if(strcmp(mode, "w") == 0)  flag |= (O_WRONLY | O_CREAT | O_TRUNC);else if(strcmp(mode, "w+") == 0) flag |= (O_WRONLY | O_CREAT | O_TRUNC | O_RDONLY);else if(strcmp(mode, "a") == 0)  flag |= (O_WRONLY | O_CREAT | O_APPEND);else if(strcmp(mode, "a+") == 0) flag |= (O_WRONLY | O_CREAT | O_APPEND | O_RDONLY);// 2. 根据打开方式打开文件mode_t m = 0666;// 默认权限int fd = 0;if(flag & O_CREAT) fd = open(path, flag, m);// 注意新建文件需要设置权限else fd = open(path, flag);// 打开失败if(fd == -1) return NULL;// 3.打开成功就创建并返回MY_FILE对象MY_FILE *newfile = (MY_FILE*)malloc(sizeof(MY_FILE));if(newfile == NULL){close(fd);return NULL;}// 4.初始化MY_FILE对象memset(newfile->_buffer, '\0', sizeof(newfile->_buffer));newfile->_current = 0;newfile->_flush = BUFFER_LINE;newfile->_fd = fd;// 5. 返回文件return newfile;
}

4. 缓冲区刷新fflush

int my_fflush(MY_FILE *stream);   //缓冲区刷新

 缓冲区冲刷是一个十分重要的动作,它决定着 IO 是否正确,这里的 my_fflush 是将用户级缓冲区中的数据冲刷至内核级缓冲区。

冲刷的本质:拷贝,用户先将数据拷贝给用户层面的缓冲区,再系统调用将用户级缓冲区拷贝给内核级缓冲区,最后才将数据由内核级缓冲区拷贝给文件。

因此 IO 是非常影响效率的。数据传输过程必须遵循冯诺依曼体系结构。

函数 fsync

  • 将内核中的数据手动拷贝给目标文件(内核级缓冲区的刷新策略极为复杂,为了确保数据能正常传输,可以选择手动刷新)

注意: 在冲刷完用户级缓冲区后(write),需要将缓冲区清空,否则缓冲区就一直满载了

// 缓冲区刷新
int my_fflush(MY_FILE *stream)
{assert(stream);// 利用系统接口将用户数据冲刷给OSint ret = write(stream->_fd, stream->_buffer, stream->_current);stream->_current = 0; // 每次刷新后,都需要清空缓冲区fsync(stream->_fd);   // 将内核中的数据强制刷给磁盘(文件)if (ret != -1) return 0;else return -1;
}

5. 数据写入fwrite

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream);    

数据写入用户级缓冲区的步骤:

  1. 判断当前用户级缓冲区是否满载,如果满了,需要先刷新,再进行后续操作
  2. 获取当前待写入的数据大小 user_size用户级缓冲区剩余大小 my_size,方便进行后续操作
  3. 如果 my_size >= user_size,说明缓冲区容量足够,直接进行拷贝;否则说明缓冲区容量不足,需要重复冲刷->拷贝->再冲刷 的过程,直到将数据全部拷贝
  4. 拷贝完成后,需要判断是否触发相应的刷新策略,比如 行刷新->最后一个字符是否为 '\n',如果满足条件就刷新缓冲区
  5. 数据写入完成,返回实际写入的字节数(简化版,即 user_size

如果是一次写不完的情况,需要通过循环写入数据,并且在缓冲区满后进行刷新,因为循环写入时,目标数据的读取位置是在不断变化的(一次读取一部分,不断后移),所以需要对读取位置和读取大小进行特殊处理

size_t my_fwrite(const void *ptr, size_t size, size_t nmemb, MY_FILE *stream)
{// 1. 先判断缓冲区是否已满if(stream->_current == BUFFER_SIZE) my_fflush(stream);// 2.根据缓冲区剩余容量情况,进行拷贝size_t user_size = size * nmemb;                 // 用户想写入的字节数size_t my_size = BUFFER_SIZE - stream->_current; // 缓冲区剩余容量size_t writen = 0;if(my_size >= user_size){// 剩余容量足够则全部拷贝memcpy(stream->_buffer+stream->_current, ptr, user_size);// 更新字段stream->_current += user_size;writen = user_size;}else{memcpy(stream->_buffer+stream->_current, ptr, my_size);// 更新字段stream->_current += my_size;writen = my_size;}// 4. 开始刷新// 4.1 全缓冲直接刷新if(stream->_flush & BUFFER_ALL){if(stream->_current == BUFFER_SIZE) my_fflush(stream);}// 4.2 行缓冲else if(stream->_flush & BUFFER_LINE){if(stream->_buffer[stream->_current-1] == '\n') my_fflush(stream);}else{//TODO}// 为了简化,这里返回用户实际写入的字节数return writen;
}

6. 文件关闭 fclose

int my_fclose(MY_FILE *fp);   //关闭文件

文件在关闭前,需要先将缓冲区中的内容进行冲刷,否则会造成数据丢失

注意: my_fclose 返回值与 close 一致,因此可以复用

// 关闭文件
int my_fclose(MY_FILE *fp)
{assert(fp);// 刷新残余数据if (fp->_current > 0)my_fflush(fp);// 关闭 fdint ret = close(fp->_fd);// 释放已开辟的空间free(fp);fp = NULL;return ret;
}

7. 测试

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>int main()
{//打开文件,写入一段话FILE* fp = fopen("log.txt", "w+");assert(fp);char inPutBuff[512] = "good morning\n";int n = fwrite(inPutBuff, 1, strlen(inPutBuff), fp);printf("本次成功写入 %d 字节的数据\n", n);fclose(fp);fp = NULL;return 0;
}

8. 小结

用户在进行文件流操作时,实际要进行至少三次的拷贝:用户->用户级缓冲区->内核级缓冲区->文件C语言 中众多文件流操作都是在完成 用户->用户级缓冲区 的这一次拷贝动作,其他语言也是如此,最终都是通过系统调用将数据冲刷到磁盘(文件)中。

在以前篇章中提过了一个疑问:为什么会打印两次 hello fprintf 就很好理解了:因为没有触发刷新条件(文件一般为全缓冲),所以数据滞留在用户层缓冲区中,fork 创建子进程后,子进程结束,刷新用户层缓冲区[子进程],此时会触发写时拷贝机制,父子进程的用户层缓冲区不再是同一个;父进程结束后,刷新用户层缓冲区[父进程],因此会看见打印两次的奇怪现象。

最后再简单提一下 printf 和 scanf 的工作原理

无论是什么类型,最终都要转为字符型进行存储,程序中的各种类型只是为了更好的解决问题
printf

  • 根据格式读取数据,如整型、浮点型,并将其转为字符串
  • 定义缓冲区,然后将字符串写入缓冲区(stdout
  • 最后结合一定的刷新策略,将数据进行冲刷

scanf

  • 读取数据至缓冲区(stdin
  • 根据格式将字符串扫描分割,存入字符指针数组
  • 最后将字符串转为对应的类型,赋值给相应的变量
  • 这也就解释了为什么要确保 输出/输入 格式与数据匹配,如果不匹配的话,会导致 读取/赋值 错误

这也就解释了为什么要确保 输出/输入 格式与数据匹配,如果不匹配的话,会导致 读取/赋值 错误

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/769341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM监控工具

JVM监控工具 文章目录 JVM监控工具jpsjmapjmap -histo 进程idjmap -heap 进程id (查看堆信息)jmap -dump:formatb,filefilename.hprof 进程id (将堆当前时刻快照信息dump到文件中) JSTACKjstack 查看死锁信息jstack找出占用cpu最高的线程堆栈信息 jinfo查看jvm参数查看java系统…

Perfetto Trace抓取

1. Perfetto简介 Perfetto 是一个用于 Android 系统的性能跟踪工具&#xff0c;可以帮助开发者分析系统性能和调试问题。 Perfetto 是 Android 10 中引入的全新平台级跟踪工具。这是适用于 Android、Linux 和 Chrome 的更加通用和复杂的开源跟踪项目。 在低于Android R的版本上…

量子计算新“尺度”:用经典计算机评估复杂量子系统!

未来的量子计算机有望在计算机科学、医疗、商业、化学、物理学等多个领域解决难题&#xff0c;从而超越传统计算机。然而&#xff0c;目前的量子计算机仍存在局限&#xff0c;主要是由于它们固有的错误率。为此&#xff0c;研究者正致力于降低这些错误率。 一种研究量子计算机误…

Linux系统部署Paperless-Ngx文档管理系统结合内网穿透实现公网访问

文章目录 1. 部署Paperless-ngx2. 本地访问Paperless-ngx3. Linux安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 Paperless-ngx是一个开源的文档管理系统&#xff0c;可以将物理文档转换成可搜索的在线档案&#xff0c;从而减少纸张的使用。它内置…

机械硬盘与固态硬盘究竟该适合选用哪种,看完本文你就了解了!

随着科技的发展,计算机存储技术经历了从传统的机械硬盘(HDD)到现代固态硬盘(SSD)的革新变迁。在这篇文章中,我们将深入探讨机械硬盘与固态硬盘在功能特点上的显著区别,帮助用户更好地理解这两种存储设备的核心优势与不足。 一、存储原理与结构差异 1. 机械硬盘(HDD) …

Midjourney AI绘图工具介绍及使用

介绍 Midjourney是一款目前被誉为最强的AI绘图工具。只要输入想到的文字&#xff0c;就能通过人工智能产出相对应的图片。 官网只是宣传和登录入口&#xff0c;提供个人主页、订阅管理等功能&#xff0c;Midjourney实际的绘画功能&#xff0c;是在另外一个叫discord的产品中实…

网络电视盒子哪个品牌好?2024畅销电视盒子排行榜

电视盒子的品牌和产品非常多&#xff0c;让新手在选购时难度增大&#xff0c;大部分消费者在此时会选择参考销量排名情况&#xff0c;小编这次结合各个电商平台的销量和用户评价整理了电视盒子排行榜&#xff0c;想买电视盒子不知道网络电视盒子哪个品牌好可以收藏。 TOP 1.泰捷…

论文导读 | 漫谈图神经网络

本文主要介绍图神经网络相关内容&#xff0c;包括图神经网络的基本结构以及近期研究进展。 背景 在实际生活中&#xff0c;许多数据都可以用图的形式表达&#xff0c;比如社交网络、分子模型、知识图谱、计算机网络等。图深度学习旨在&#xff0c;显式利用这些数据中的拓扑结…

3.18_C++_day6_作业

作业要求&#xff1a; 程序代码&#xff1a; #include <iostream>using namespace std;class Animal { private:string name;string color;int *age; public://无参构造函数Animal(){cout << "Animal::无参构造函数" << endl;}//有参构造函数Anim…

[Linux初阶]which-find-grep-wc-管道符命令

目录 一.which 二.find a.-name b.-size 三.grep 四.wc 五.管道符(|) 五.总结 一.which 语法格式: which [命令] Linux中的一个个命令,本体上就是一个个的二进制可执行程序(相当于windows中的.exe文件). 在Linux中,一切皆文件. which命令:用于查看指定命令的可执行…

【JS进阶】第3天

JavaScript 进阶 - 第3天笔记 理论较多&#xff0c;主要讲解原型对象和对象原型 了解构造函数原型对象的语法特征&#xff0c;掌握 JavaScript 中面向对象编程的实现方式&#xff0c;基于面向对象编程思想实现 DOM 操作的封装。 了解面向对象编程的一般特征掌握基于构造函数原…

饼图渲染的关键

1) 创建一个DOM对象,有自定义的高和宽. 2) 引入Echarts软件包并导入到对应文件内 npm i Echarts import 文件.js script src.../文件 3) 初始化一个对象 4) 对象的方法实现饼图渲染 data内的数据,且当一个对象已经渲染一遍,再执行这个,会对setOption的参数进行更新,其…

Adobe Illustrator和Photoshop哪个难学?另一款好用设计软件上位!

当设计开始时&#xff0c;几乎没有人不知道。 Adobe 公司的两大设计软件&#xff1a;Adobe Illustrator 和 Photoshop。虽然 Adobe Illustrator和 Photoshop 很有名&#xff0c;有一定设计经验的设计师在前期探索使用后可以对 Adobe Illustrator和 Photoshop 的使用差异有一个大…

NEC 78K系列MCU概述

一.初识 NEC MCU NEC&#xff0c;即日本电气株式会社&#xff0c; 经营半导体业务。 NEC 倡导“ ALL Flash”&#xff0c;即 MCU 内的程序存储器使用 Flash ROM。 为什么用 Flash ROM&#xff1f; 与掩膜 ROM 微控制器相比&#xff0c; Flash 微控制器加速了系…

理解java进程和多线程

一、进程是什么&#xff0c;线程是什么&#xff1f; (1)理解&#xff1a; 进程是一个主体任务&#xff0c;线程是这个进程下的子任务&#xff0c;下图解释&#xff1a; 描述&#xff1a;一个餐厅好比一个进程&#xff0c;一个餐厅下面有多个职位的厨师&#xff0c;他们分别是…

Java代码基础算法练习-公式求和-2024.03.24

任务描述&#xff1a; 求公式Snaaaaaa…aa…aaa&#xff08;有n个a&#xff09;之值&#xff0c;其中a是一个数字&#xff0c;为2。 例如&#xff0c;n5 时222222222222222&#xff0c;n 由键盘输入(n<5)。 任务要求&#xff1a; package march0317_0331;import java.util.…

React函数组件Hook

问题: 相对于类组件, 函数组件的编码更简单, 效率也更高, 但函数组件不能有state (旧版) 解决: React 16.8版本设计了一套新的语法来让函数组件也可以有state Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性 Hook也叫钩子…

Simple negative sampling for link prediction inknowledge graphs

摘要 知识图嵌入方法学习知识图中实体和关系的低维向量表示&#xff0c;便于知识图中的链接预测任务。在学习嵌入过程中&#xff0c;采样负三元组是很重要的&#xff0c;因为KGs只观察到正三元组。据我们所知&#xff0c;均匀随机、基于生成对抗网络(GAN)和nscach、结构感知负…

Linux 系统 快速卸载docker

(卸载前一定要做好相关数据的备份) 卸载&#xff1a; 第一种卸载方法 1、查询docker安装过的包&#xff1a; yum list installed | grep docker 2、删除安装包&#xff1a; yum remove docker-ce.x86_64 ddocker-ce-cli.x86_64 -y 3、删除镜像/容器等 rm -rf /var/lib/dock…

【征稿进行时|见刊、检索快速稳定】2024年教育资源与人文发展国际学术会议(ICERHD 2024)

【征稿进行时|见刊、检索快速稳定】2024年教育资源与人文发展国际学术会议&#xff08;ICERHD 2024&#xff09; 2024 International Conference on Educational Resources and Humanistic Development&#xff08;ICERHD 2024&#xff09; 大会主题&#xff1a; 教育艺术 商…