当前位置: 首页 > 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;
}

相关文章:

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

&#x1f307;前言 在 C语言 的文件流中&#xff0c;存在一个 FILE 结构体类型&#xff0c;其中包含了文件的诸多读写信息以及重要的文件描述符 fd&#xff0c;在此类型之上&#xff0c;诞生了 C语言 文件相关操作&#xff0c;如 fopen、fclose、fwrite 等&#xff0c;这些函数…...

ArcPy错误处理与调试技巧(3)

三、调试技巧 调试是编程过程中不可或缺的一部分&#xff0c;以下是一些常用的调试技巧&#xff1a; 1. 打印调试信息 在代码中添加print语句&#xff0c;可以帮助你了解程序的运行状态和变量的值。例如&#xff1a; # 打印提示信息&#xff0c;表示开始执行缓冲区分析 print(…...

小程序使用npm包的方法

有用的链接 npm init -y 这个命令很重要, 会初始化 package.json 再重新打开微信小程序开发工具 选择工具中npm构建 在程序中引用时在main.js中直接使用包名的方式引用即可 如安装的是generator包&#xff0c;npm构建后就会生成 const myPackage require(***-generato…...

Asp.Net Core SignalR的协议协商问题

文章目录 前言一、协议协商的原理二、常见的协商问题及解决办法1.跨域资源共享&#xff08;CORS&#xff09;问题2.身份验证和授权问题3.传输方式不兼容问题4.路由配置错误5.代理和负载均衡器问题6.自定义协商&#xff08;高级&#xff09; 总结 前言 在ASP.NET Core SignalR …...

Rust 学习笔记:发布一个 crate 到 crates.io

Rust 学习笔记&#xff1a;发布一个 crate 到 crates.io Rust 学习笔记&#xff1a;发布一个 crate 到 crates.io提供有用的文档注释常用标题文档注释作为测试注释所包含的项目 使用 pub use 导出一个方便的公共 API设置 crates.io 账户添加 metadata 到一个新的 crate发布到 c…...

剪枝中的 `break` 与 `return` 区别详解

在回溯算法的剪枝操作中&#xff1a; if (sum candidates[i] > target) break;这个 break 既不等效于 return&#xff0c;也不会终止整个回溯过程。它只会终止当前层循环的后续迭代&#xff0c;而不会影响其他分支的回溯。让我用图解和示例详细说明&#xff1a; &#x1…...

Spring Boot 4.0实战:构建高并发电商系统

Spring Boot 4.0作为Java生态的全新里程碑&#xff0c;首次原生支持虚拟线程&#xff08;Virtual Threads&#xff09;与Project Loom特性&#xff0c;单机QPS处理能力较3.x版本提升5-8倍。本文以电商系统为实战场景&#xff0c;深度解析Spring Boot 4.0在微服务架构、分库分表…...

Vert.x学习笔记-EventLoop与Context的关系

Vert.x学习笔记 1. EventLoop 的核心作用2. Context 的核心作用3. EventLoop 与 Context 的关系1. 事件循环&#xff08;EventLoop&#xff09;的核心职责2. 上下文&#xff08;Context&#xff09;的核心职责3. 事件循环与上下文的关系&#xff08;1&#xff09;一对一绑定&am…...

2025030给荣品PRO-RK3566开发板单独升级Android13的boot.img

./build.sh init ./build.sh -K ./build.sh kernel 【导入配置文件】 Z:\Android13.0\rockdev\Image-rk3566_t\config.cfg 【更新的内核】 Z:\Android13.0\rockdev\Image-rk3566_t\boot.img 【导入分区表&#xff0c;使用原始的config.cfg会出错的^_】 Z:\Android13.0\rockdev\…...

由enctype-引出post与get的关系,最后深究至请求/响应报文

本篇载自我的笔记&#xff0c;本次为第二次复习。我觉得我有能力理一下思路了。 --- 笔记截图。 enctype HTML 表单的 enctype&#xff08;Encode Type&#xff0c;编码类型&#xff09;属性用于控制表单数据在提交到服务器时的编码方式&#xff0c;不同取值的详细解析如下&a…...

排序算法衍生问题

排序算法衍生问题 引言 排序算法是计算机科学中基础且重要的算法之一&#xff0c;其应用广泛&#xff0c;如数据统计分析、数据库操作、网络排序等。随着计算机科学的发展&#xff0c;排序算法的研究不仅局限于传统的排序方法&#xff0c;还衍生出许多有趣且实用的衍生问题。…...

Mac电脑上本地安装 redis并配置开启自启完整流程

文章目录 一、安装 Redis方法 1&#xff1a;通过源码编译安装&#xff08;推荐&#xff09;方法 2&#xff1a;通过 Homebrew 安装&#xff08;可选&#xff09; 二、配置 Redis1. 创建配置文件和数据目录2. 修改配置文件 三、配置开机自启1、通过 launchd 系统服务&#xff08…...

STP(生成树协议)原理与配置

冗余链路与环路问题 冗余链路虽然提供网络可靠性&#xff0c;但会引发环路问题。广播风暴导致网络资源耗尽&#xff0c;MAC地址表频繁更新造成震荡&#xff0c;同一数据帧通过不同路径重复传输影响数据完整性。 STP工作机制 生成树协议通过选举机制消除环路&#xff0c;同时…...

搭建基于VsCode的ESP32的开发环境教程

一、VsCode搜索ESP-IDF插件 根据插件处搜索找到ESP-IDF并安装 安装完成 二、配置安装ESP-IDF 配置IDF 按照如下配置&#xff0c;点击安装 安装完成 三、使用案例程序 创建一个闪光灯的例子程序&#xff0c;演示程序编译下载。 选择blink例子&#xff0c;闪烁LED的程序 选…...

【MFC】初识MFC

目录 01 模态和非模态对话框 02 静态文本 static text 01 模态和非模态对话框 首先我们需要知道模态对话框和非模态对话框的区别&#xff1a; 模态对话框是一种阻塞时对话框&#xff0c;它会阻止用户与应用程序的其他部分进行交互&#xff0c;直到用户与该对话框进行交互并关…...

C++.二分法教程

二分法 1. 问题引入1.1 猜数字游戏2.1 二分法核心思想为什么需要二分法&#xff1f;二分法的基本步骤示例代码代码解析 2.2 二分法适用场景有序数组查找效率要求高示例场景示例代码代码解析 3.1 初始化左右边界示例代码代码解析 3.2 计算中间值示例代码代码解析 3.3 判断与更新…...

如何通过数据分析优化项目决策

通过数据分析优化项目决策需从明确数据分析目标、选择适当的数据分析工具、确保数据质量、建立数据驱动文化等方面入手&#xff0c;其中&#xff0c;明确数据分析目标是优化决策过程的基础&#xff0c;只有清晰明确的数据分析目标才能指导有效的数据采集与分析&#xff0c;避免…...

2024年数维杯国际大学生数学建模挑战赛B题空间变量协同估计方法研究解题全过程论文及程序

2024年数维杯国际大学生数学建模挑战赛 B题 空间变量协同估计方法研究 原题再现&#xff1a; 在数理统计学中&#xff0c;简单采样通常假设来自相同总体的采样点彼此独立。与数理统计相反&#xff0c;空间统计假设空间变量的采样点是相依的&#xff0c;并在其值中表现出某些趋…...

leetcode hot100刷题日记——34.将有序数组转换为二叉搜索树

First Blood&#xff1a;什么是平衡二叉搜索树&#xff1f; 二叉搜索树&#xff08;BST&#xff09;的性质 左小右大&#xff1a;每个节点的左子树中所有节点的值都小于该节点的值&#xff0c;右子树中所有节点的值都大于该节点的值。 子树也是BST&#xff1a;左子树和右子树也…...

thinkphp 5.1 部分知识记录<一>

1、配置基础 惯例配置->应用配置->模块配置->动态配置 惯例配置:核心框架内置的配置文件,无需更改。应用配置:每个应用的全局配置文件(框架安装后会生成初始的应用配置文件),有部分配置参数仅能在应用配置文件中设置。模块配置:每个模块的配置文件(相同的配置…...

RAG:面向知识密集型自然语言处理任务的检索增强生成

摘要 大型预训练语言模型已被证明能够在其参数中存储事实性知识,并在下游自然语言处理(NLP)任务的微调中取得了最先进的结果。然而,它们访问和精准操作知识的能力仍然有限,因此在知识密集型任务中,其表现落后于针对特定任务设计的架构。此外,如何为它们的决策提供出处(…...

MVVM、MVC的区别、什么是MVVM

一、什么是MVVM &#xff08;一&#xff09;定义 MVVM是Model - View - ViewModel的缩写&#xff0c;它是一种软件架构设计模式&#xff0c;主要用于构建用户界面。这种模式将应用程序分为三个主要部分&#xff1a; Model&#xff08;模型层&#xff09; 它是应用程序中负责…...

网页自动化部署(webhook方法)

实现步骤&#xff1a; 宝塔安装宝塔WebHook 2.5插件。 github 上配置网页仓库&#xff08;或可在服务器的网页根目录clone&#xff09;。 配置宝塔WebHook 2.5 添加hook脚本&#xff1b; 编辑添加syncJC脚本&#xff1b; #!/bin/bash # 定义网站根目录 WEBROOT"/www…...

线性代数入门:轻松理解二阶与三阶行列式的定义与理解

前言 行列式是线性代数中一个非常基础但又极其重要的概念。它不仅是解线性方程组的利器&#xff0c;还在矩阵理论、向量空间、特征值等问题中扮演着关键角色。今天&#xff0c;我将用最通俗易懂的方式&#xff0c;向高中生朋友们介绍二阶和三阶行列式的基本概念和计算方法。让…...

AU6825集成音频DSP的2x32W数字型ClaSSD音频功率放大器(替代TAS5825)

1.特性 ● 输出配置 - 立体声 2.0: 2 x 32W (8Ω,24V,THD N 10%) - 立体声 2.0: 2 x 26W (8Ω,21V,THD N 1%) ● 供电电压范围 - PVDD:4.5V -26.4V - DVDD: 1.8V 或者 3.3V ● 静态功耗 - 37mA at PVDD12V ● 音频性能指标 - THDN ≤ 0.02% at 1W,1kHz - SNR ≥ 107dB (A-wei…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1商用服务体验全流程

华为云 Flexus 与 DeepSeek-V3/R1 的深度整合&#xff0c;构建了一套 “弹性算力 智能引擎” 的协同体系。 Flexus 系列云服务器基于柔性计算技术&#xff0c;通过动态资源调度&#xff08;如 Flexus X 实例&#xff09;实现 CPU / 内存的实时弹性分配&#xff0c;尤其适合大模…...

Go语言的原子操作

当我们想要对某个变量并发安全的修改&#xff0c;除了使用官方提供的mutex&#xff0c;还可以使用sync/atomic包的原子操作&#xff0c;它能够保证对变量的读取或修改期间不被其他的协程所影响。 Golang提供的原子操作都是非侵入式的&#xff0c;由标准库sync/atmoic包提供&am…...

Visual Studio 2022 插件推荐

Visual Studio 2022 插件推荐 Visual Studio 2022 (简称 VS2022) 是一款强大的 IDE&#xff0c;适合各类系统组件、框架和应用的开发。插件是接入 VS2022 最重要的扩展方式之一&#xff0c;它们可以大幅提升开发效率、优化代码质量&#xff0c;并提供强大的调试和分析功能。 …...

【深度学习-pytorch篇】3. 优化器实现:momentum,NAG,AdaGrad,RMSProp,Adam

Optimization Algorithms Explained 1. Beale Function 与导数函数讲解 Beale 函数是一个著名的用于测试优化算法性能的函数&#xff0c;其具有多个局部极值点&#xff0c;适合评估不同优化器的表现&#xff1a; def beale(x1, x2):"""Beale 函数定义&#x…...

C# NX二次开发-查找连续倒圆角面

在QQ群里有人问怎么通过一个选择一个倒圆角面来自动选中一组倒圆角面。 可以通过ufun函数 UF_MODL_ask_face_type 和 UF_MODL_ask_face_props 可判断处理选择相应的一组圆角面。 代码: Tag[] 查找连续倒圆角面(Tag faceTag) {theUf.Modl.AskFaceType(faceTag, out int typ…...