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

17.基础IO_3

文章目录

  • 基础IO_3
    • 1、认识缓冲区
      • 1.1 缓冲区的本质和存在的意义
      • 1.2 缓冲区的发送策略
      • 1.3 如何理解`fwrite`写入
    • 2、通过现象进一步剖析
      • 2.1 示例代码
      • 2.2 实验现象
      • 2.3 缓冲区详解
    • 3、用 C 实现自己的缓冲输出系统
      • 3.1 整体模块结构
      • 3.2 结构体定义:`FILE_`
      • 3.3 `fopen_()`:打开文件并初始化结构体
        • 功能流程:
        • 注意点:
      • 3.4 `fwrite_()`:写入缓冲区并模拟行缓冲刷新
        • 功能流程:
        • 注意点:
      • 3.5 `fclose()`:刷新剩余数据并释放资源
        • 功能流程:
        • 注意点:
      • 3.6 主函数调用流程

基础IO_3

这一部分很难理解,我会尽量讲解的通俗易懂

1、认识缓冲区

1.1 缓冲区的本质和存在的意义

  1. 缓冲区的本质就是一块内存
  2. 存在的意义
    • 首先是可以协调速度上的差异,在计算机中,CPU处理速度很快,而磁盘、网络、打印机等设备相对较慢。缓冲区就像一个中转站,让数据先暂存在内存中,等设备准备好再慢慢处理。
    • 其次可以减少系统的调用次数,每次调用 read() 或 write() 都会涉及系统资源。如果每次只处理一点点数据,效率会很低。缓冲区可以积累一批数据后一次性处理,节省时间和资源。

简单来说就是节省进程进行数据IO的时间。


1.2 缓冲区的发送策略

策略名称触发时机适用场景特点与说明
无缓冲每次调用写入函数时立即发送错误日志、实时输出直接写入设备,效率低但实时性高
行缓冲遇到换行符 \n 或缓冲区满时发送终端输入输出、日志文件适合处理文本行,用户体验好
全缓冲缓冲区满时发送,或手动刷新大文件写入、批量数据处理提高效率,减少系统调用次数,但实时性较差

举个例子:

  • 你在终端输入一行命令,按下回车后才执行 → 行缓冲
  • 程序写入大量数据到文件,只有缓冲区满了才写出 → 全缓冲
  • 程序写入错误信息,立即显示 → 无缓冲

1.3 如何理解fwrite写入

一句话总结一下,fwrite与其理解成是写入文件的函数,不如理解成这是一个拷贝函数,把进程拷贝到对应的缓冲区之中。接下来让我们一起来看看代码的输出现象!

2、通过现象进一步剖析

2.1 示例代码

#include<stdio.h>
#include<unistd.h>
#include<string.h>
int main()
{// C接口printf("c语言提供\n");fprintf(stdout,"c语言提供\n");const char* cString = "c语言提供\n";fputs(cString,stdout);// 系统接口const char* wString = "系统接口\n";write(1,wString,strlen(wString));fork();return 0;
}

2.2 实验现象

  1. 当我在终端直接运行命令,输出结果
    在这里插入图片描述
  2. 当我让他重定向到log.txt之中时,输出结果变为
    在这里插入图片描述

2.3 缓冲区详解

我们根据这个实验现象首先可以退出两个结论

  1. 这个实验现象一定时和缓冲区有关的。
  2. 缓冲区一定不在内核之中,不然的话write也会打印两次。也就是说我们之前所有谈论到的缓冲区都是用户级别的语言层面为我们提供的缓冲区!

接下来解释一下这个实验现象

  1. 在我们没有进行重定向之前,是向终端之中打印,也就是说整个的打印策略是行缓冲打印FILE之中包含了缓冲区,所以在fork()之前,这个缓冲区里就已经什么都没有了!再进行一点很重要的解释也是我之前存在的一个很严重的误区,子进程直接完全继承父进程的整个内存空间,公用这么一块空间,并且父进程已经将缓冲区文件输出,所以都是空的。
  2. 接下来我们解释进行重定向之后的实验结果,此时的行缓冲打印策略注意!!变成了全缓冲,也就是说只有等到缓冲区域满了才会进行刷新,很显然这么几条数据没有填满缓冲区,全部存储在了父进程FILE的缓冲区之中了。这个时候子进程继承,等**到进程退出的时候,这时发生写时拷贝!!**所以最终的数据也显示了两倍。
  3. 为什么write没有进行现实呢? 因为这是系统所提供的接口直接使用的是fd,与FILE无关(或者说时根本就没有这一部分),所以数据也无法存储到FILE中的缓冲区之中。因此只会显示一次

3、用 C 实现自己的缓冲输出系统

前面已经讲到缓冲区都是用户级别的语言层面为我们提供的,我们能不能够利用系统操作函数,简单去实现一下呢?
接下来一起来看一看
非常好!我们先来梳理你这套自定义 I/O 系统的代码结构脉络,理清每个函数的职责、调用关系和缓冲机制的设计思路。这样你后续写博客或继续扩展功能时会更有条理。


3.1 整体模块结构

要实现的是一个简化版的 stdio.h,主要包含三个核心函数:

函数名功能描述关键点
fopen_()打开文件并初始化自定义 FILE_ 结构体封装 open(),设置缓冲区
fwrite_()写入数据到缓冲区,遇换行符触发刷新模拟行缓冲,使用 write() 写出
fclose()刷新剩余数据并关闭文件清理资源,释放结构体

此外,还定义了一个自定义结构体 FILE_,用于模拟标准库中的 FILE 类型。


3.2 结构体定义:FILE_

这是整个系统的核心数据结构,封装了文件描述符和缓冲区:

#define SIZE 1024
#define SYNC_LINE 1typedef struct {int fileno;             // 文件描述符int pos;                // 当前缓冲区写入位置int flags;              // 缓冲策略标志(如行缓冲)char buffers[SIZE];     // 缓冲区
} FILE_;

设计思路:

  • fileno 关联底层文件(我们要写入的文件)
  • buffers 存储待写入数据
  • pos 追踪写入位置
  • flags 控制缓冲策略(目前只支持行缓冲)

3.3 fopen_():打开文件并初始化结构体

FILE_ *fopen_(const char*path_name,const char* mode)
{int flag =0;flag |= O_CREAT;if(strcmp(mode,"r") == 0){flag |= O_RDONLY;}else if(strcmp(mode,"w") == 0) {flag |= O_WRONLY | O_TRUNC;}else if(strcmp(mode,"rw") == 0) {flag |= O_RDWR;}else{return NULL;}// 接下来我么就是打开文件int fd = open(path_name,flag,0666);if(fd == -1) return NULL;// 我们最后需要返回的就是一个结构体指针// 现在我们手里已经有了fd,并且我们还得让数据存储在我们的缓存区里面哇FILE_* fp = (FILE_*)malloc(sizeof(FILE_));if (!fp) {close(fd);return NULL;}fp->fileno = fd;fp->pos = 0;fp->flags = SYNC_LINE;// 默认进行行缓冲memset(fp->buffers,0,SIZE);// 初始化缓冲区return fp;
}
功能流程:
  1. 根据 mode 设置 open() 的标志位,open本身是并不会去创建一个FILE结构体的。
  2. 调用 open() 获取文件描述符fd
  3. 分配并初始化 FILE_ 结构体
  4. 设置默认行为为行缓冲
注意点:
  • 支持 "r""w""rw" 三种模式
  • 使用 O_CREAT 保证文件存在
  • 如果 mallocopen 失败,及时释放资源

3.4 fwrite_():写入缓冲区并模拟行缓冲刷新

// 对文件中进行写入
int fwrite_(void *ptr,int num, FILE_*fp)
{// 要确保传入的全部都不是空的否则就返回-1if(ptr == NULL | num == 0 | fp == NULL) return -1;// 数据传入自定义缓冲区的过程for (int i = 0; i < num; ++i) {char ch = ((char*)ptr)[i];fp->buffers[fp->pos++] = ch;    }//int written = write(fp->fileno,fp->buffers,num);// 这样就把数据全部写入到我们的缓冲区域之中去了// 应该采取什么样子的方式去读取换行符号呢for (int i = 0;i < fp->pos; ++i) {if (fp->buffers[i] == '\n') {write(fp->fileno,fp->buffers,i);fp->pos = 0;break; // 找到换行符就停止}}// 解读出第一个换行符号然后再进行打印输出return 0;
}
功能流程:
  1. 将数据逐字节写入 fp->buffers,也就是写入我们自己创建的一个缓冲区之中去
  2. 每写入一个字符,检查是否为 \n
  3. 如果遇到 \n,调用 write() 写出缓冲区内容
  4. 写出后清空缓冲区(或移动剩余数据)
注意点:
  • 模拟行缓冲:只有遇到 \n 才触发刷新
  • 使用 write() 直接写入文件描述符
  • 没有 \n 的数据将暂存在缓冲区中

3.5 fclose():刷新剩余数据并释放资源

void fclose(FILE_* fp)
{// 关掉文件就比较简单了// 0、在关掉之前我应该先去将缓冲区的内容全部进行输出if(fp->pos != 0)write(1,fp->buffers,(fp->pos));// 1、调用系统关掉文件close(fp->fileno);// 2、整个结构体指针free(fp);
}
功能流程:
  1. 检查缓冲区是否还有未写出的数据
  2. 如果有,调用 write() 写出
  3. 关闭文件描述符
  4. 释放结构体内存
注意点:
  • 确保所有数据都被写出,避免丢失
  • 使用 close()free() 清理资源

3.6 主函数调用流程

int main() {FILE_* fp = fopen_("test.txt", "w");if (!fp) return 1;char msg[] = "Hello, world!\nThis is a test.\n";fwrite_(msg, strlen(msg), fp);fclose(fp);return 0;
}

调用顺序:

  • fopen_()fwrite_()fclose()

http://www.dtcms.com/a/515366.html

相关文章:

  • Ubuntu 系统掉电导致分区损坏无法启动修复方案
  • 相机模组,模组是什么意思?
  • 申威架构ky10安装php-7.2.10.rpm详细步骤(国产麒麟系统64位)
  • STM32F407 通用定时器
  • lodash-es
  • 股票交易网站建设四会市城乡规划建设局网站
  • API技术深度解析:从基础原理到最佳实践
  • 西安今晚12点封城吗龙岩网站优化
  • 使用有限体积法求解双曲型守恒性方程(一)FV 框架
  • jenkins流水线部署springboot项目
  • YOLOv5:目标检测的实用派王者
  • 《工业之心:Blender 工业场景解构》
  • 【Linux网络】应用层自定义协议
  • unity免费改名工具-Mulligan Renamer
  • Git分支的多人协作
  • 服务外包网站成都住建局官网app
  • 【ABAP函数】+ALSM_EXCEL_TO_INTERNAL_TABLE批导长字段
  • 艺术学院网站模板wordpress二手车模板
  • docker api 常用接口
  • flutter鸿蒙:实现类似B站或抖音的弹幕功能
  • 从静态模型到数据驱动:图观模型编辑器让工程设备真实还原
  • 了解Docker的多阶段构建(Multi-stage Build)
  • [特殊字符] Berry.Live:开箱即用的.NET直播流媒体服务器
  • 网站模板的修改宝安企业网站建设
  • 网站开发软件费用2018网站流量怎么做
  • 数据结构:顺序表讲解(1)
  • 第二次作业-第二章时间服务
  • Python爬虫实战:获取香港恒生指数历史数据与趋势分析
  • 【Frida Android】基础篇11:Native层hook基础——修改原生函数的返回值
  • 什么是DNS负载均衡?提升网站稳定性与容错性的方法