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

深圳网站建设价格是多少做一个自己的网站

深圳网站建设价格是多少,做一个自己的网站,商城类网站建设,网站自己可以做么✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 文章目录 🌇前言🏙️正文1、FILE 结构设计2、函数使用及分析3、文件打开 fopen4、文件关闭 fclose5、缓…

✨个人主页: 北 海
🎉所属专栏: Linux学习之旅
🎃操作环境: CentOS 7.6 阿里云远程服务器

成就一亿技术人


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、FILE 结构设计
    • 2、函数使用及分析
    • 3、文件打开 fopen
    • 4、文件关闭 fclose
    • 5、缓冲区刷新 fflush
    • 6、数据写入 fwrite
    • 7、数据读取 fread
    • 8、实际效果
    • 9、小结
    • 10、源码
  • 🌆总结


🌇前言

C语言 的文件流中,存在一个 FILE 结构体类型,其中包含了文件的诸多读写信息以及重要的文件描述符 fd,在此类型之上,诞生了 C语言 文件相关操作,如 fopenfclosefwrite 等,这些函数本质上都是对系统调用的封装,因此我们可以根据系统调用和缓冲区相关知识,模拟实现出一个简单的 C语言 文件流

图解

本文重点模拟实现 FILEC语言 文件操作相关函数

注意: 本文实现的只是一个简单的 demo,重点在于理解系统调用及缓冲区


🏙️正文

1、FILE 结构设计

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

图解
图片来源:《Linux基础IO》 - 2021dragon

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

#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语言 文件操作相关函数前,需要先来简单回顾下


2、函数使用及分析

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

  • fopen 打开文件
  • fclose 关闭文件
  • fflush 进行缓冲区刷新
  • fwrite 对文件中写入数据
  • fread 读取文件数据
#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;
}

结果

#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

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

结果

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


3、文件打开 fopen

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

打开文件分为以下几步:

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

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

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

// 打开文件
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;elseflags |= 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);elsefd = 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_FILEmemset(new_file->_buffer, '\0', BUFFER_SIZE); // 初始化缓冲区new_file->_current = 0;                       // 下标置0new_file->_flush = BUFFER_LINE;               // 行刷新new_file->_fd = fd;                           // 设置文件描述符return new_file;
}

4、文件关闭 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;
}

5、缓冲区刷新 fflush

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

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

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

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

函数 fsync

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

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

// 缓冲区刷新
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;
}

6、数据写入 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)
{// 写入先判断缓冲区是否已满if (stream->_current == BUFFER_SIZE)my_fflush(stream);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{// 一次写不完,需要分批写入size_t tmp = user_size; // 用于定位 ptr 的读取位置while (user_size > my_size){// 一次写入 my_size 个数据。user_size 会减小memcpy(stream->_buffer + stream->_current, ptr + (tmp - user_size), my_size);stream->_current += my_size; // 切记实时更新下标my_fflush(stream);           // 写入后,刷新缓冲区user_size -= my_size;my_size = BUFFER_SIZE - stream->_current;}// 最后空间肯定足够,再把数据写入缓冲区中memcpy(stream->_buffer + stream->_current, ptr + (tmp - user_size), user_size);stream->_current += user_size;writen = tmp;}// 通过刷新方式,判断是否进行刷新if (stream->_flush & BUFFER_NONE){// 无缓冲,直接冲刷my_fflush(stream);}else if (stream->_flush & BUFFER_LINE){// 行缓冲,遇见 '\n' 才刷新if (stream->_buffer[stream->_current - 1] == '\n')my_fflush(stream);}else{// 全缓冲,满了才刷新if (stream->_current == BUFFER_SIZE)my_fflush(stream);}// 为了简化,这里返回用户实际写入的字节数,即 user_sizereturn writen;
}

7、数据读取 fread

在进行数据读取时,需要经历 文件->内核级缓冲区->用户级缓冲区->目标空间 的繁琐过程,并且还要考虑 用户级缓冲区是否能够一次读取完所有数据,若不能,则需要多次读取

注意:

  • 读取前,如果用户级缓冲区中有数据的话,需要先将数据刷新给文件,方便后续进行操作
  • 读取与写入不同,读取结束后,需要考虑 \0 的问题(在最后一个位置加),如果不加的话,会导致识别错误;系统(内核)不需要 \0,但C语言中的字符串结尾必须加 \0,现在是 系统->用户(C语言)
// 数据读取
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缓冲区中,再赋给 ptrif (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;
}

8、实际效果

现在通过自己写的 myStdio 测试C语言文件流操作

#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] = "2110年1月1日,距离地球能源完全枯竭还有3650天。为了解决地球能源危机,\n人类制造了赛尔机器人和宇宙能源探索飞船赛尔号,去寻找神秘精灵看守的无尽能源。";int n = fwrite(inPutBuff, 1, strlen(inPutBuff), fp);printf("本次成功写入 %d 字节的数据", n);fclose(fp);printf("\n==============================\n");//重新打开文件fp = fopen("log.txt", "r");assert(fp);char outPutBuff[512] = { '\0' };n = fread(outPutBuff, 1, sizeof(outPutBuff), fp);printf("本次成功读取 %d 字节的数据,具体内容为: \n%s\n", n, outPutBuff);fclose(fp);fp = NULL;return 0;
}

结果:

结果
下面是库函数的结果:

结果
可以看出结果是一样的


9、小结

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

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

结果

最后再简单提一下 printfscanf 的工作原理

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

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

scanf

  • 读取数据至缓冲区(stdin
  • 根据格式将字符串扫描分割,存入字符指针数组
  • 最后将字符串转为对应的类型,赋值给相应的变量

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


10、源码

关于 myStdio 的源码可以点击下方链接进行获取

模拟实现C语言文件流

图


🌆总结

以上就是本次关于 Linux【模拟实现C语言文件流】的全部内容了,通过 系统调用+缓冲区,我们模拟实现了一个简单版的 myStdio 库,在模拟实现过程中势必会遇到很多问题,而这些问题都能帮助你更好的理解缓冲区的本质:提高 IO 效率


星辰大海

相关文章推荐

Linux基础IO【重定向及缓冲区理解】

Linux基础IO【文件理解与操作】

===============

Linux【模拟实现简易版bash】

Linux进程控制【进程程序替换】

Linux进程控制【创建、终止、等待】

http://www.ds6.com.cn/news/87607.html

相关文章:

  • 好学校平台网站模板下载不了可以发广告的100个网站
  • 中国建设银行河北省分行官方网站seo网络优化是什么意思
  • 外国人在中国注册公司需要什么条件天津seo建站
  • 网站开发和设计seo技术培训
  • 网站建设发布ps科技感武汉百度
  • 小鱼儿企业网站管理系统网站推广投放
  • 做简历网站知乎在线seo优化工具
  • 随州网站建设多少钱百度指数的搜索指数
  • 网站建设南阳广州网络seo优化
  • 天津网站建设技术托管利尔化学股票股吧
  • 广西贵港建设集团有限公司网站seochinaz查询
  • 北京网站seo外包线上营销活动主要有哪些
  • 南宁做网站开发的公司有哪些苏州关键词优化怎样
  • 企业做网站价格网络营销论文毕业论文
  • 源码网站制作教程免费找客源软件
  • 项目计划书ai生成山东关键词优化联系电话
  • 网站在线客服模板seo品牌
  • 洪洞网站建设怎么制作个人网站
  • 免费源码分享提供seo顾问服务适合的对象是
  • 泰州市做网站化工网站关键词优化
  • 新闻网站开发方案百度手机app下载并安装
  • 30岁零基础学室内设计seo顾问是什么
  • 可以浏览违禁网址的浏览器网站优化公司推荐
  • 用php做网站出现的问题如何让百度能查到自己
  • 企业网站的目的百度认证考试
  • 商丘市做网站soso搜搜
  • 美国电话号码生成器江门seo
  • 上海企业信息公示系统官网windows优化大师的优点
  • 国外前端 网站windows优化大师可靠吗
  • 扶余网站建设网站友链查询源码