I/O进程(Day26)

一、学习内容

I/O进程

  #### 标准IO

  1.

     ##### 概念

     1. 针对文件的读写操作 文件IO最终达成的目的:将一个临时存在于内存中的数据,永久性的存放于磁盘当中

  2.

     ##### 操作

     1. 文件IO的操作,需要这样的2个指针 一个指针:指向源数据,提供读取操作的指针 另一个指针:指向用来接受数据的文件的地址 该指针需要提前准备:并且确定好该指针到底指向哪个文件
  #### 创建一个文件指针

  1.

     ##### fopen

     1.

        ###### 函数原型

        1. FILE \*fopen(const char \*pathname, const char \*mode);

     2.

        ###### 函数描述

        1. 打开一个文件,成功打开之后,返回指向该文件的文件指针

     3.

        ###### 调用形式

        1.

           ```cpp
            FILE* fp = fopen("文件名","r");
               FILE* fp = fopen("文件名","w");
               FILE* fp = fopen("文件名","a");
               FILE* fp = fopen("文件名","r+");
               ....
               if(fp == NULL){
                   printf("文件打开失败\n")
                   return 0;    
               }
           ```

     4.

        ###### 函数返回值

        1. 成功返回指向打开文件的文件指针,失败返回NULL

     5.

        ###### 参数分析

        1.

           ###### 参数 pathname

           1. 准备打开的文件的路径名

        2.

           ###### 参数 mode

           1. 文件的打开形式

        3.

           ###### r

           1. 以只读的形式,打开文件,文件成功打开之后,光标位于文件的开头

           2. 注意:如果文件不存在,r打开会失败,并且fopen函数返回一个NULL

           3. 光标位于文件的开头:光标在文件中第一个数据的地址上

           4. 光标位于文件的末尾:光标在文件的结束符的地址上,文件结束符的前一个数据,大概率不是文件中的最后一个数据,而是一个换行符

        4.

           ###### r+

           1. 以读写的形式打开文件,其他内容和r一样

           2. 注意:r+存在读写2个光标 读写2个光标是独立管理的,但是如果不做特殊操作的话,读写两个光标是同时移动的

        5.

           ###### w

           1. 以只写的形式打完开文件,如果文件存在,则清空文件内容后打开,如果文件不存在,则创建文件后打开。文件成功打开后,光标定位在文件的开头 大概率上来说,以w打开文件,一般都会成功 只有在文件打开数量超过系统允许的上限的情况下,才会打开失败

           2. 系统默认允许的文件打开上限为:1024个 也可以使用shell指令: ulimit -a 查看

        6.

           ###### w+

           1. 以读写的形式打开文件,剩下的就和w是一样了

        7.

           ###### a

           1. 以追加的形式打开文件,如果文件不存在则创建文件后打开,如果文件存在,不会清空文件内容,直接打开。文件打开后,光标位于文件的末尾

        8.

           ###### a+

           1. 以追加写和读的形式打开文件啊,如果文件不存在,则创建后打开。如果文件存在,则直接打开。

           2. 注意:文件打开后,读光标位于文件的开头,并随着文件读取慢慢的向后移动。写光标总是位于文件的末尾,并不会随着读光标的移动而移动
  #### 关闭一个文件

  1.

     ##### fclose

     1.

        ###### 函数原型

        1. int fclose(FILE \*stream);

     2.

        ###### 函数描述

        1. 通过给定的文件指针stream关闭文件

     3.

        ###### 函数调用

        1. fclose(fp)
  #### 文件指针FILE\*类型

  1.

     ##### FILE本质上是一个typedef的结构体

     1.

        ###### 查看系统自带的结构体类型

        1.

           ```cpp
           cd /usr/include    专门用来存放头文件的目录
           sudo ctags -R      在存放头文件的目录里面,创建一个搜索列表
           vim -t 想要查看的数据名    前两步是一次性操作,之后想要查看其他内容,直接执行第三步即可
           ```

           1.

              ###### 输入 vim -t FILE 显示如下画面,我们要的是 红框中的内容,所以键入1,按回车

              1. ![](https://i-blog.csdnimg.cn/direct/c1586943b6034d3785dfc9e6c9d7eb68.png)

           2.

              ###### 查看后得知,FILE typedef 自 _IO_FILE 类型,继续追踪,鼠标点到 _IO_FILE 上面,键入 ctrl+ \]

              1. ![](https://i-blog.csdnimg.cn/direct/45c0dd91f3e74bd2ad5922d6df94db2c.png)

           3.

              ###### 此时又会找到2个搜索目标,应该是第一个,键入1,按回车

              1. ![](https://i-blog.csdnimg.cn/direct/92dee89d0c11486d9ace766315ccdf56.png)

     2.

        ###### FILE的定义如

        1.

           ```cpp
           struct _IO_FILE {
             int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */


             /* The following pointers correspond to the C++ streambuf protocol. */
             /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
             char* _IO_read_ptr;   当前读光标的位置
             char* _IO_read_end;   文件结束符的位置
             char* _IO_read_base;  /* Start of putback+get area. */
             char* _IO_write_base; /* Start of put area. */
             char* _IO_write_ptr;  写光标的位置
             char* _IO_write_end;  文件结束符为止
             char* _IO_buf_base;   /* Start of reserve area. */
             char* _IO_buf_end;    /* End of reserve area. */


             int _fileno; 最核心的,最终通过该数据定位文件的                                                                 

           };
           ```
  #### 3个特殊的FILE\* 文件

  1.

     ##### stdin

     1.

        ###### 指向了终端标准输入流文件的指针

        1. scanf,gets,getchar 等,都是默认的使用了这个文件流指针

  2.

     ##### stdout

     1.

        ###### 指向了终端标准输出流文件的指针

        1. printf,puts,putchar 等,都是默认的使用了这个文件流指针

  3.

     ##### stderr

     1.

        ###### 指向了终端标准错误流的指针

        1.

           ###### 标准错误流

           1. 目前来说流向只有2种,代码流向终端 以及 终端流向代码 标准错误流:属于代码流向终端

              1. stdout也属于代码流向终端,那么stderr 和 stdout的区别

                 1. stderr的存在目的就是为了区分stdout,从而实现 正常信息通过 stdout输出到终端 错误信息通过 stderr 可选择的输出到任意文件中 从而实现信息的划分

              2. 谁在用stderr

                 1. perror函数

                    1. 函数原型

                       1. void perror(const char \*s);

                    2. 功能描述

                       1. perror函数,先输出s(提示信息):然后会根据一个标准的错误编号,输出该错误编号所描述的错误内容 错误代码哪来的:最后一个调用的系统函数或者库函数,因为错误调用而产生的 说人话就是:perror输出因为什么原因发生了错误
  #### 针对文件的第一组读写函数

  1.

     ##### fputc

     1.

        ###### 函数原型

        1. int fputc(int c, FILE \*stream);

     2.

        ###### 功能描述

        1. 将c的字符形式,写入到stream指向的文件中去

     3.

        ###### 调用形式

        1. FILE\* fp = fopen(文件名,"w") fputc(字符,fp)

     4.

        ###### 参数解析

        1. 参数 c:想要输出的数据

        2. 参数 stream:准备接受数据的文件的地址

  2.

     ##### fgetc

     1.

        ###### 函数原型

        1. int fgetc(FILE \*stream);

     2.

        ###### 功能描述

        1. 从stream指向的文件中,读取1个字节的数据

     3.

        ###### 参数描述

        1. 参数 stream:想要读取的文件的地址

     4.

        ###### 返回值

        1. 成功读取,会以 unsigned char的形式,返回读取到的那一个字节的数据

        2. 如果读取到文件结束符,则返回 EOF (-1) 如果读取发生错误,也会返回 EOF
  #### 针对文件的第二组读写函数

  1.

     ##### fprintf / sprintf

     1.

        ###### 函数原型

        1. int printf(const char \*format, ...); int fprintf(FILE \*stream, const char \*format, ...);

        2. int sprintf(char \*str, const char \*format, ...);

     2.

        ###### 功能描述

        1. printf功能:将字符串format中的内容输出到stdout指向的文件中去(stdout指向终端) fprintf功能:将字符串format中的内容输出到stream指向的文件中去(stream指向哪个文件由fopen函数决定)

        2. 将format中的数据写入str指向的字符数组中去 相当于实现了把任意类型的数据,转换成字符串的功能

     3.

        ###### 调用形式

        1.

           ```cpp
            FILE* fp = fopen(文件名,"w")
               fprintf(fp,"%d",123)
               fprintf(fp,"%c",'c')
               fprintf(fp,"%s","abc")
               fprintf(fp,"%s","123")
               ....
           ```

        2.

           ```cpp
           har str[20] = {0};
               sprintf(str,"%lf",3.1416)
               最终 str == "3.1416"
           ```

     4.

        ###### 参数解析

        1. 第一个参数 stream:指向接受数据的文件的指针

        2. 第二个参数 format:想要输出到文件中的数据

        3. 第三个参数 ...:format中格式占位符具体表示的数据

        4. 注意:fprintf,无论将什么数据写入文件后,这些数据都会以字符串的形式进行保存

     5.

        ###### 总结

        1. fprintf/sprintf 整体上来说用法一模一样,只不过不同的printf将数据写入的地方是不一样的

        2. printf:默认将数据写入终端

        3. fprintf:将数据写入第一个参数stream指向的文件中去

        4. sprintf:将数据写入第一个参数str指向的字符数组中去

  2.

     ##### fscanf / sscanf

     1.

        ###### fscanf

        1.

           ###### 函数原型

           1. int fscanf(FILE \*stream, const char \*format, ...);

        2.

           ###### 功能描述

           1. 从stream指向的文件中读取数据,写入到 format 中格式占位符所代表的变量中去

        3.

           ###### 调用形式

           1. FILE\* fp = fopen(文件名,"r") int a = 0; fscanf(fp,"%d",\&a)

        4.

           ###### 参数解析

           1. 第一个参数 stream:指向了想要读取数据的文件的指针

           2. 第二个参数 format:只含有格式占位符的字符串

           3. 第三个参数 ... :格式占位符代表的变量的地址

           4. 注意: %s除了空格和回车不吸收以外,其他都吸收 %d只吸收int类型数据,碰到int类型以外的数据,立刻停止吸收 %lf吸收int和浮点型,遇到单独.和除.以外的其他字符,立刻停止吸收 %c吸收一切,包括空格和回车

        5.

           ###### 返回值

           1. 成功吸收,返回吸收到的数据的项数,一般就是描述符的个数

           2. 如果文件吸收完毕 或者 吸收发生错误,则返回 EOF

     2.

        ###### sscanf

        1.

           ###### 函数原型

           1. int sscanf(const char \*str, const char \*format, ...);

        2.

           ###### 功能描述

           1. 从字符串/字符数组 str中吸收数据,写入到format中的格式占位符所代表的变量的地址上面去 实际实现:将字符串类型的数据,转换成可转换的任意类型数据

        3.

           ###### 函数调用

           1.

              ```cpp
               char str[32] = "123abc"
                  int a = 0;
                  char b[20] = {0};
                  sscanf(str,"%d%s",&a,b);
                  最终 a == 123,b=="abc"
              ```

脑图

二、作业

题目1、

cpp 复制代码
有如下结构体:
typedef struct Student{
    char name[20];
    int id;
    double chinese;//语文成绩
    double math;
    double english;
    double physical;
    double chemical;
    double biological;
}stu_t;

有一个 stu_t的结构体数组 arr[3];随便使用任何方式初始化这个数组中的3个结构体

编写2个函数 :save_stu 和 load_stu
save_stu:通过 fprintf 将arr数组中的3个学生的所有信息,保存到文件中去
load_stu:通过 fscanf 将文件中的3个学生的所有信息,读取到一个新的结构体数组中,并输出所有学生的信息

代码解答:

cpp 复制代码
#include <stdio.h>
#include <string.h>

// 定义一个学生结构体,包含学生的姓名、学号及各科成绩
typedef struct Student {
    char name[20];        // 学生姓名,最多 19 个字符(+1 个 '\0' 结束符)
    int id;               // 学生学号
    double chinese;       // 语文成绩
    double math;          // 数学成绩
    double english;       // 英语成绩
    double physical;      // 物理成绩
    double chemical;      // 化学成绩
    double biological;    // 生物成绩
} stu_t;

// 输入学生信息
void arr_input(stu_t arr[]) {
    int i;  // 循环变量
    for(i = 0; i < 3; i++) {  // 循环 3 次,输入 3 个学生的信息
        printf("请输入第%d个学生信息\n", i + 1);  // 提示输入第几个学生
        printf("姓名: ");  // 提示输入姓名
        scanf("%s", arr[i].name);  // 读取学生姓名并存入结构体
        printf("学号: ");  // 提示输入学号
        scanf("%d", &arr[i].id);  // 读取学生学号并存入结构体
        printf("语文成绩: ");  // 提示输入语文成绩
        scanf("%lf", &arr[i].chinese);  // 读取语文成绩
        printf("数学成绩: ");  // 提示输入数学成绩
        scanf("%lf", &arr[i].math);  // 读取数学成绩
        printf("英语成绩: ");  // 提示输入英语成绩
        scanf("%lf", &arr[i].english);  // 读取英语成绩
        printf("物理成绩: ");  // 提示输入物理成绩
        scanf("%lf", &arr[i].physical);  // 读取物理成绩
        printf("化学成绩: ");  // 提示输入化学成绩
        scanf("%lf", &arr[i].chemical);  // 读取化学成绩
        printf("生物成绩: ");  // 提示输入生物成绩
        scanf("%lf", &arr[i].biological);  // 读取生物成绩
    }
}

// 保存学生信息到文件
void save_stu(stu_t arr[]) {
    FILE *fp = fopen("students.txt", "w");  // 打开文件 "students.txt",以写入模式
    if (fp == NULL) {  // 检查文件是否成功打开
        printf("文件打开失败!\n");  // 文件打开失败时输出错误信息
        return;  // 返回
    }

    for (int i = 0; i < 3; i++) {  // 循环 3 次,保存 3 个学生的信息
        // 使用 fprintf 将每个学生的信息格式化写入文件
        fprintf(fp, "%s %d %.2f %.2f %.2f %.2f %.2f %.2f\n",
            arr[i].name, arr[i].id, arr[i].chinese, arr[i].math,
            arr[i].english, arr[i].physical, arr[i].chemical, arr[i].biological);
    }

    fclose(fp);  // 关闭文件
    printf("学生信息已保存到文件\n");  // 提示信息保存成功
}

// 从文件加载学生信息
void load_stu(stu_t arr[]) {
    FILE *fp = fopen("students.txt", "r");  // 打开文件 "students.txt",以读取模式
    if (fp == NULL) {  // 检查文件是否成功打开
        printf("文件打开失败!\n");  // 文件打开失败时输出错误信息
        return;  // 返回
    }

    for (int i = 0; i < 3; i++) {  // 循环 3 次,读取 3 个学生的信息
        // 使用 fscanf 从文件中读取学生的信息,并存入结构体数组
        fscanf(fp, "%s %d %lf %lf %lf %lf %lf %lf",
            arr[i].name, &arr[i].id, &arr[i].chinese, &arr[i].math,
            &arr[i].english, &arr[i].physical, &arr[i].chemical, &arr[i].biological);
    }

    fclose(fp);  // 关闭文件

    // 输出从文件加载的学生信息
    printf("从文件加载的学生信息:\n");
    for (int i = 0; i < 3; i++) {  // 循环输出每个学生的信息
        printf("姓名: %s, 学号: %d, 语文: %.2f, 数学: %.2f, 英语: %.2f, 物理: %.2f, 化学: %.2f, 生物: %.2f\n",
            arr[i].name, arr[i].id, arr[i].chinese, arr[i].math,
            arr[i].english, arr[i].physical, arr[i].chemical, arr[i].biological);
    }
}

int main() {
    stu_t arr[3];  // 定义一个学生结构体数组,包含 3 个学生
    
    arr_input(arr);  // 调用函数输入 3 个学生的信息
    save_stu(arr);   // 调用函数将学生信息保存到文件
    load_stu(arr);   // 调用函数从文件加载学生信息并输出

    return 0;  // 程序正常结束
}

成果展现:

三、总结

学习内容概述:

1. I/O进程基础:

I/O进程是操作系统中的一个重要部分,负责管理输入输出操作。

涉及标准输入、标准输出、文件描述符等概念。

包括阻塞、非阻塞I/O、异步I/O等不同I/O模型。

2. 具体的I/O操作方式:

`read()`和`write()`:系统调用实现读取和写入操作。

缓冲区的管理:包括内核缓冲区与用户缓冲区的交互。

文件的打开、关闭、定位等操作,使用`open()`, `close()`, `lseek()`等系统调用。

3. 进程管理与I/O的关:

进程可以通过I/O操作与外部世界进行交互。

进程阻塞和非阻塞在I/O操作中的应用,如何避免因I/O操作导致的进程停滞。

学习难点:

1. 阻塞与非阻塞I/O模型的理解与应用:

如何区分阻塞和非阻塞操作,以及这两者对进程调度和CPU利用率的影响。

在实际应用中选择合适的I/O模型。

注意事项:

1. 系统调用的返回值检查:

对每次系统调用的返回值进行检查,确保I/O操作的成功与失败能够被正确处理,避免资源泄露。

2. 缓冲区大小的设置:

使用合适大小的缓冲区,以平衡系统资源与性能,避免内存浪费或I/O频繁的上下文切换。

3. 文件描述符的管理:

在处理大量文件描述符时,确保正确管理文件描述符,防止句柄泄漏。

未来学习重点:

1. 深入理解各类I/O模型的优缺点:

继续深入学习阻塞、非阻塞、异步I/O的工作机制,掌握在不同场景下选择合适的I/O模型。

2. 多进程/多线程与I/O操作的结合:

探索如何通过多进程或多线程编程来提高I/O处理的并发性和效率,特别是在高性能计算或服务器开发中的应用。

3. 优化I/O性能:

探讨如何通过系统级优化、缓存管理、批量操作等手段提高I/O操作的效率。

4. 掌握高效的文件I/O处理方法:

针对大数据量文件的读写操作,学习使用内存映射文件(mmap)等高效手段,进一步提升I/O性能。

相关推荐
为什么每天的风都这么大2 小时前
Vscode/Code-server无网环境安装通义灵码
ide·vscode·阿里云·编辑器·ai编程·code-server
陌上阳光10 小时前
vscode连接远程开发机报错
ide·vscode·编辑器
羊子雄起12 小时前
CKEditor前端样式和编辑器的样式不一致的问题
前端·编辑器
界面开发小八哥14 小时前
「Java EE开发指南」如何使用Visual JSF编辑器设计JSP?(一)
java·ide·java-ee·编辑器·myeclipse
Bio Coder16 小时前
vim 一次注释多行 的几种方法
linux·编辑器·vim·注释·快捷键·方法·取消注释
luckilyil1 天前
前端—Cursor编辑器
前端·编辑器
一棵开花的树,枝芽无限靠近你1 天前
【PPTist】开源PPT编辑器初体验
编辑器·powerpoint
Random_index1 天前
#开发环境篇:vscode里面登录已同步设置的提示1怎么取消
ide·vscode·编辑器
赵闪闪1681 天前
Node.js 安装与环境配置详解:从入门到实战
node.js·编辑器·vim
风流野趣fly2 天前
好用的 IDEA 插件
java·编辑器·intellij-idea