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

news2024/11/17 3:52:42

目录

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

    // 利用系统接口将用户数据冲刷给OS
    int 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);

    // 关闭 fd
    int 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.coloradmin.cn/o/1542104.html

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

相关文章

【云开发笔记No.6】腾讯CODING平台

腾讯云很酷的一个应用&#xff0c;现在对于研发一体化&#xff0c;全流程管理&#xff0c;各种工具层出不穷。 云时代用云原生&#xff0c;再加上AI&#xff0c;编码方式真是发生了质的变化。 从前&#xff0c;一个人可以写一个很酷的软件&#xff0c;后来&#xff0c;这变得…

Day08 Java复习8 Spring MVC概念

Day09 Java复习9 Spring MVC spring mvc 的核心组件是什么&#xff1f; DispatcherServlet 1.JAVA 和Spring 、Spring Boot 、Spring MVC的关系 你要举办一个生日派对&#xff0c;而且你希望它既特别又好玩。Java就像是举办派对的地方&#xff0c;Spring、Spring Boot和Spri…

javaSSM公司招聘管理系统IDEA开发mysql数据库web结构计算机java编程maven项目

一、源码特点 IDEA开发SSM公司招聘管理系统是一套完善的完整企业内部系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;MAVEN方式加 载&#xff0c;系统具有完整的源代码和…

银行监管报送系统介绍(六):客户风险数据报送系统

【概念定义】 银监会决定从2013年起实行新版客户风险统计制度&#xff0c;对各政策性银行、国有商业银行、股份制商业银行进行客户信息汇总统计。 客户风险统计信息&#xff0c;是指新版客户风险统计报送信 息。客户风险统计报送信息包括但不限于对公及同业客户授信和 表内外业…

设计模式(十二):中介者模式(行为型模式)

Mediator&#xff0c;中介者模式&#xff1a;用一个中介对象封装一些列的对象交互。属于行为型模式 Facade&#xff0c;外观模式&#xff1a;为子系统中的一组接口提供一致的界面&#xff0c;facade 提供了一高层接口&#xff0c;这个接口使得子系统更容易使用。属于结构型模式…

Rust并发编程thread多线程和channel消息传递

安全高效的处理并发是 Rust 诞生的目的之一&#xff0c;主要解决的是服务器高负载承受能力。 并发&#xff08;concurrent&#xff09;的概念是指程序不同的部分独立执行&#xff0c;这与并行&#xff08;parallel&#xff09;的概念容易混淆&#xff0c;并行强调的是"同…

npm、nrm、nvm详解与应用

本文全面介绍了 npm、nrm 以及 nvm 这三个与 Node.js 开发密切相关的工具。首先&#xff0c;对 npm 进行了定义和功能解释&#xff0c;包括其在依赖管理、项目管理、脚本执行、版本控制和社区贡献等方面的作用。接着&#xff0c;详细介绍了 npm 的常用命令和设置下载源的操作&a…

python综合实战案例-数据分析

Python是进行数据分析的好工具&#xff0c;今天就是借助一个案例给大家进行数据分析讲解。 本例设计一个log.txt⽂件&#xff0c;该文件记录了某个项⽬中某个 api 的调⽤情况&#xff0c;采样时间为每分钟⼀次&#xff0c;包括调⽤次数、响应时间等信息&#xff0c;⼤约18万条数…

java数据结构与算法刷题-----LeetCode451. 根据字符出现频率排序

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. hash统计出现次数后排序2. 桶排序 1. hash统计出现次数后排序…

WebClient 同步、异步调用实现对比

文章目录 一、概述二、pom依赖三、代码结构四、源码传送1、异步代码2、同步代码3、完整代码 一、概述 WebClient是Spring WebFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具&#xff0c;从Spring5.0开始WebClient作为RestTemplete的替代品&#xff0c;有…

初探Flink集群【持续更新】

周末下雨&#xff0c;倒杯茶&#xff0c;在家练习Flink相关。 开发工具&#xff1a;IntelliJ Idea 第一步、创建项目 打开Idea&#xff0c;新建Maven项目&#xff0c;包和项目命名 在pom.xml 文件中添加依赖 <properties><flink.version>1.13.0</flink.vers…

python(django(自动化))之流程接口展示功能前端开发

1、创建模板代码如下&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>测试平台</title> </head> <body role"document"> <nav class "navbar n…

轻量级 C++ UI 库:快速、可移植、自包含 | 开源日报 No.168

ocornut/imgui Stars: 53.4k License: MIT imgui 是 C 的无臃肿图形用户界面&#xff0c;具有最小的依赖关系。 该项目的主要功能、关键特性、核心优势包括&#xff1a; 为 C 提供了一个轻量级的图形用户界面库输出优化的顶点缓冲区&#xff0c;可在 3D 渲染应用程序中随时呈…

2024/03/24----Pycharm社区版本下载以及创建一个新项目

1.本科安装过这个软件&#xff0c;因为系统重置了&#xff0c;所以重头再来一遍。 2.链接 社区版本Pycharm 3.我下载了2023.1.15的版本&#xff0c;越前面一些的版本可能会比较稳定 4.安装步骤 &#xff08;1&#xff09;先把文件安装目录改成其他盘(文件名最好不要有数字和符号…

2025汤家凤考研数学视频,基础网课百度网盘课程+PDF讲义资料

2025汤家凤大神及数学全程 docs.qq.com/doc/DTmtOa0Fzc0V3WElI 复制粘贴到浏览器&#xff0c;可以见所有的Ke 第一轮 夯实基础 1.阅读大纲考查要求&#xff0c;明确每章的学习目标&#xff1b; 2.按节学习数学理论基础知识&#xff0c;吃透书中例题&#xff1b; 3.学习每章…

Django 三板斧、静态文件、request方法

【一】三板斧 【1】HttpResponse &#xff08;1&#xff09;介绍 HttpResponse是Django中的一个类&#xff0c;用于构建HTTP响应对象。它允许创建并返回包含特定内容的HTTP响应。 &#xff08;2&#xff09;使用 导入HttpResponse类 from django.http import HttpResponse创…

Python学习从0到1 day18 Python可视化基础综合案例 1.折线图

我默记这段路的酸楚&#xff0c;等来年春暖花开之时再赏心阅读 —— 24.3.24 python基础综合案例 数据可视化 — 折线图可视化 一、折线图案例 1.json数据格式 2.pyecharts模块介绍 3.pyecharts快速入门 4.数据处理 5.创建折线图 1.json数据格式 1.什么是json 2.掌握如何使用js…

【云开发笔记No.9】Kanban与敏捷开发

Kanban看板起源于丰田。 看板&#xff08;Kanban&#xff09;一词来自日文&#xff0c;本义是可视化卡片。如下图所示&#xff0c;看板工具的实质是&#xff1a;后道工序在需要时&#xff0c;通过看板向前道工序发出信号——请给我需要数量的输入&#xff0c;前道工序只有得到看…

阿里云4核16G服务器优惠价格26元1个月、149元半年

阿里云4核16G服务器优惠价格26.52元1个月、79.56元3个月、149.00元半年。2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也…

[iOS]GCD(一)

[iOS]GCD(一) 文章目录 [iOS]GCD(一)GCD的概要GCD的APIDispatch Queuedispatch_queue_createMain Dispatch_set_target_queuedispatch_afterDispatch Groupdispatch_barrier_asyncdispatch_applydispatch_applydispatch_suspend/dispatch_resumeDispatch Semaphoredispatch_onc…