文件IO流

IO使用函数

标准IO文件IO(低级IO)
打开fopen, freopen, fdopenopen
关闭fcloseclose
getc, fgetc, getchar, fgets, gets, fread printf fprintfread
putc, fputc, putchar, fputs, puts, fwrite scanf fscanfwrite
操作文件指针fseeklseek
其它fflush rewind ftell

文件描述符

当打开文件时,文件相关信息(如创建时间、所属者、操作权限等)会被加载到内存,操作系统用struct file结构体维护这些信息,多个打开的文件对应的struct file通过双向链表管理。

每个进程的进程控制块task_struct中,有一个指针files指向files_struct表。该表包含一个指针数组,数组中的每个指针指向内存中的一个struct file,实现进程与文件的连接。文件描述符是该数组的下标,通过它可定位内存中的文件。

数组前三个下标通常固定对应标准输入、标准输出和标准错误(perror)。

这三个文件描述符(0号-标准输入,1号-标准输出,2号-标准错误)在UNIX/Linux系统中被预设为进程的输入和输出通道,是为了方便程序与用户或者其他程序进行通信。

  • 标准输入(stdin):默认情况下,这是键盘输入的数据。如果程序需要从用户处接收输入,它就会从这个描述符读取数据。

  • 标准输出(stdout):默认情况下,这是显示器的输出。如果程序需要向用户显示信息,它就会写入这个描述符。

  • 标准错误(stderr):默认情况下,这同样是显示器的输出。不过,它专门用于输出错误信息。这样做的好处是,即使标准输出被重定向到其他位置(如文件),错误信息仍然可以显示给用户看。

这种设计可以大大增加程序的灵活性。例如,你可以通过重定向机制,将一个程序的标准输出作为另一程序的标准输入,这就形成了所谓的“管道”(pipe),能将多个程序连接起来,协同完成一项工作。这也是UNIX/Linux广受欢迎的一个重要原因。

总结:文件修饰符本质是一个数字,用于表示要操作文件在系统里抽象的文件指针数组里的下标,通过下标(文件修饰符)就可以通过函数来间接操作文件。

1:open函数

调用低级io相关函数一般我们会引入以下头文件:

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
  • <fcntl.h>: 定义了open函数和相关的常量,如O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL等。

  • <unistd.h>: 定义了read, write, lseek, 和close函数,以及很多UNIX标准的系统调用。

  • <sys/types.h>: 通常包含了一些用于系统调用的数据类型定义,比如off_t, size_t等。

  • <sys/stat.h>: 如果你在调用open函数时需要设置文件权限,这个头文件中定义了文件权限相关的宏,比如S_IRUSR, S_IWUSR

调用open()函数可打开或创建一个文件,其函数原型如下:

int open(const char *pathname, int flags)  
int open(const char *pathname, int flags, mode_t mode)  
  • 返回值:返回一个新的文件描述符,指向打开的文件,如果调用失败则返回-1。如果返回-1,那么c程序会同时将错误信息状态码设为文件打开失败的对应信息,可以通过perror来显示

  • 参数

    • pathname:要打开或创建的文件的路径.相对于当前目录或使用绝对路径

    • flags:指定程序对文件的操作权限和其他的设置

      • O_RDONLY 以只读方式打开文件:

      • O_WRONLY 以只写方式打开文件

      • O_RDWR 以可读写方式打开文件。上述三种旗标是互斥的,也就是不可同时使用,但可与下列的旗标利用OR(|)运算符组合。

      • O_CREAT 若欲打开的文件不存在则自动建立该文件。

      • O_EXCL 如果O_CREAT 也被设置,此指令会去检查文件是否存在。文件若不存在则建立该文件,否则将导致打开文件错误。

        此外,若O_CREAT与O_EXCL同时设置,并且欲打开的文件为符号连接,则会打开文件失败。

      • O_APPEND 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式加入到文件后面。追加模式(写入时指针位于文件末尾)。

      • O_SYNC 以同步的方式打开文件。

  • mode:用于设置当文件创建时的权限,只有使用了O_CREAT,即创建文件才会生效。比如0664指所属用户以及用户组都是读写权限,其他用户则只有读权限(r:4 w:2 X:1)(x执行)

    • 第一个6表示文件创建者的权限

    • 第二个6表示文件所属组的权限

    • 第三个4表示其他用户的权限

  • 示例代码:

int main()
{char *filename = "new_file.txt";int fd = open(filename, O_RDWR | O_CREAT , 0666);if (fd == -1){perror("无法创建文件");return 1;}return 0;
}
2:write函数

调用函数write()可向已打开的文件中写入数据,其函数模型如下:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数说明
  • fd(文件描述符)

    :这是一个整数,作用是标识要写入数据的目标文件或设备。

    • STDOUT_FILENO(标准输出,一般对应的值是 1),数据会默认输出到控制台。

    • STDERR_FILENO(标准错误,通常值为 2),用于输出错误信息。

    • 还可以是通过open()函数打开文件时返回的文件描述符。

  • buf:它是一个指向内存缓冲区的指针,里面存放着要写入磁盘的数据,一般是数组的地址。

  • count:表示要写入数据的字节大小,通常就是数组的大小。

返回值情况
  • 成功时:函数会返回实际写入的字节数,这个返回值通常和参数count的值相同。

  • 失败时:函数会返回 - 1,并且会设置errno变量来指示具体的错误类型。

  • 部分写入情况:要是返回值大于 0 但小于count,这表明只写入了部分数据。此时,你需要再次调用write()函数,从缓冲区的剩余部分继续写入。

示例代码
1. 向标准输出(控制台)写入数据
#include <unistd.h>
​
int main()
{char message[] = "Hello, World!\n";write(STDOUT_FILENO, message, sizeof(message) - 1);// 也可以直接使用1代替STDOUT_FILENOreturn 0;
}
2. 向文件写入数据
int main() 
{char *filename = "new_file.txt";int fd = open(filename, O_RDWR | O_CREAT , 0666);if (fd == -1){perror("无法创建文件");return 1;}char buffer[] = "这是一个测试文件\n";ssize_t bytes_written = write(fd, buffer, sizeof(buffer) - 1);//sizeof(buffer) - 1 等价于 strlen(buffer),减去‘\0',不需要写入文件,只写入有效字符。if (bytes_written == -1) {perror("写入失败");} else {printf("成功写入 %zd 字节\n", bytes_written);}
​close(fd);return 0;
}
3. 处理部分写入的情况
int main() 
{int fd = open("data.txt", O_RDWR | O_CREAT, 0666);char buffer[] = "这是一个较长的数据,可能需要多次写入才能完成\n";size_t total_bytes = sizeof(buffer) - 1;//sizeof(buffer)计算数组总大小(包含字符串结尾的\0),减 1 后得到有效数据长度(不包含\0)。size_t bytes_written = 0;//记录已成功写入的字节数,初始化为 0。while (bytes_written < total_bytes)//只要未写入的字节数大于 0,就继续循环。{ssize_t result = write(fd, buffer + bytes_written, total_bytes - bytes_written);//buffer + bytes_written:指针偏移到未写入部分的起始位置。//total_bytes - bytes_written:计算剩余未写入的字节数。//result:本次实际写入的字节数(可能小于请求写入的字节数)。if (result == -1){perror("写入失败");break;}bytes_written += result;}
​close(fd);return 0;
}

关键注意事项

  1. 文件打开模式的影响:如果文件是以O_APPEND模式打开的,那么每次调用write()时,数据都会被追加到文件的末尾,而文件偏移量会自动更新。

  2. 错误处理的重要性:在调用write()函数后,一定要检查返回值,以此来确保数据成功写入。常见的错误原因有磁盘已满、达到文件大小限制或者权限不足等。

  3. 写入二进制数据write()函数可以用于写入任意类型的数据,包括二进制数据。不过在处理二进制数据时,要特别注意字节顺序和数据对齐的问题。

  4. 缓冲区和系统调用:由于write()是一个系统调用,频繁调用会带来一定的开销。所以,在实际应用中,通常会先将数据缓存到内存中,然后再批量写入。

3:read函数

调用函数read()可从打开的文件中读取数据:

#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count);
​
  • 参数

    • fd 文件描述符,这个文件描述符可以通过open()获得,也可以是标准输入STDIN_FILENO(值为 0)。

    • buf 读取到的数据要存放到的地方(缓冲区地址),一般是一个数组的地址

    • count 要读取的最大字节数

  • 返回值

    • 成功:

      • 返回实际读取的字节数(范围是 0 到count

      • 若返回值为 0,说明已经到达文件末尾(EOF),没有更多数据可读取。

    • 失败:

      • 返回-1

  • 注意

    • 读普通文件时,在读到要求字节数之前已经达到了文件结尾。eg:若在达到文件尾端之前有30个字节,而要求读50个字节,则read返回30,下次在调用read时将返回0;

    • 当从终端设备读时, 通常一次最多读一行。

    • 当从网络读时,网络中的缓冲机制可能造成返回值小于所要求读的字节数。

  • 示例代码:

    1. 从标准输入读取数据
    int main() 
    {char buffer[100] = { 0 };ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));if (bytes_read == -1){perror("读取失败");return 1;}printf("成功读取 %zd 字节\n", bytes_read);return 0;
    }
2. 从文件读取数据(循环读取直到文件末尾)
int main()
{int fd = open("data.txt", O_RDONLY);if (fd == -1){perror("文件打开失败");return 1;}
​char buffer[50];ssize_t bytes_read;
​while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {// 处理读取到的数据,例如打印write(STDOUT_FILENO, buffer, bytes_read);}
​if (bytes_read == -1){perror("读取失败");}
​close(fd);return 0;
}

总结:

  • 单次调用read()返回 0 → 未读取到任何数据。

  • 循环中read()返回 0 → 文件已全部读完。

4:close函数

当打开一个文件之后,就会占据系统资源,如果我们使用完之后不及时清理,会造成资源的浪费,所以一般使用之后就会关闭文件,释放占用的资源

#include <unistd.h>
int close(int fd);

注意:关闭已打开文件时会释放该进程加在该文件中上的所有记录锁。当一个进程终止时,内核自动关闭其所有已打开的文件。很多程序都利用这一功能而不是在程序中用函数close()关闭已打开的文件

5:lseek函数

每个文件都有一个与之相关联的文件偏移量,用以度量文件起始位置到当前位置的字节数。通常情况下,读写操作都是从当前文件偏移量处开始的,读写完成后,文件偏移量会自动增加所读写的字节数。打开一个文件后,该文件偏移量默认设置为0。通过设置偏移量就可以实现文件指定位置进行插入内容。

  调用函数lseek()可显式的为打开的文件设置文件偏移量,用于改变文件的读写位置。其函数原型如下:

off_t lseek(int fd, off_t offset, int whence);
  • 参数

    • fd: 文件描述符,是一个整数,表示要操作的文件。

    • offset: 表示相对于whence所指定位置的字节数。这个值可以是正数(向文件末尾方向移动)、负数(向文件开头方向移动)或者 0(保持当前位置不变)。

    • whence: 参考点,表示偏移量的起始位置。

      • whence的值可以是以下三个常量之一: SEEK_SET:从文件开始处计算偏移量(文件起始位置加offset个字节)。

    SEEK_CUR:从当前位置计算偏移量(文件当前位置加offset个字节)。 ​ SEEK_END:从文件结束处计算偏移量(文件末尾位置加offset个字节)。

  • 返回值

    • 成功: 返回新的文件偏移量,这个值是从文件起始位置开始计算的字节数。

    • 失败:返回-1

函数lseek()仅将当前文件的文件偏移量记录在内核中,并不会引起任何I/O的操作,该文件偏移量用于下一次读写操作 。

示例代码:

1. 将文件偏移量设置到文件开头
int main()
{int fd = open("data.txt", O_RDWR);if (fd == -1){perror("文件打开失败");return 1;}
​// 将文件偏移量设置到文件开头off_t new_offset = lseek(fd, 0, SEEK_SET);if (new_offset == -1){perror("lseek失败");close(fd);return 1;}
​// 现在可以从文件开头开始读取或写入close(fd);return 0;
}
2. 计算文件大小
int main() 
{int fd = open("data.txt", O_RDONLY);if (fd == -1) {perror("文件打开失败");return 1;}// 将文件偏移量设置到文件末尾,并获取新的偏移量(即文件大小)off_t file_size = lseek(fd, 0, SEEK_END);if (file_size == -1){perror("lseek失败");close(fd);return 1;}printf("文件大小为 %lld 字节\n", (long long)file_size);close(fd);return 0;
}
3. 定位到文件中间位置进行读取
int main()
{int fd = open("data.txt", O_RDONLY);if (fd == -1) {perror("文件打开失败");return 1;}
​// 定位到文件中间位置off_t file_size = lseek(fd, 0, SEEK_END);lseek(fd, file_size / 2, SEEK_SET);
​// 从文件中间位置开始读取数据char buffer[100];ssize_t bytes_read = read(fd, buffer, sizeof(buffer));if (bytes_read > 0) {// 处理读取到的数据}
​close(fd);return 0;
}
写一个程序来实现文件复制功能:
  1. #include <stdio.h>
    #include <stdbool.h>
    #include <string.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    ​
    int main(int argc, char *argv[]) 
    {char *fileName = "test.txt";char *otherName = "copy.txt";// 1: 打开文件:------------------------------int fd = open(fileName, O_RDWR );int fd2 = open(otherName, O_RDWR | O_CREAT, 0666 );if(fd == -1 || fd2 == -1){perror("文件创建失败!");exit(EXIT_FAILURE);}// 2: 对文件进行读或写操作:--------------------// 2-1: 读进数据char buf[64] = {0};int length = sizeof(buf) / sizeof(buf[0]);ssize_t size;while((size = read( fd, buf, length )) != 0){// 2-2: 写出数据write( fd2, buf , size );}// 3: 关闭文件 ------------------------------close(fd);close(fd2);puts("---end---");return 0;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/83415.shtml

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

相关文章

云原生DMZ架构实战:基于AWS CloudFormation的安全隔离区设计

在云时代,传统的DMZ(隔离区)概念已经演变为更加灵活和动态的架构。本文通过解析一个实际的AWS CloudFormation模板,展示如何在云原生环境中构建现代化的DMZ安全架构。 1. 云原生DMZ的核心理念 传统DMZ是网络中的"缓冲区",位于企业内网和外部网络之间。而在云环境…

一、虚拟货币概述

1. 定义 - 虚拟货币是一种基于网络技术、加密技术和共识机制的数字货币&#xff0c;它不依赖传统金融机构发行&#xff0c;而是通过计算机算法生成&#xff0c;例如比特币、以太坊等。 2. 特点 - 去中心化&#xff1a;没有一个单一的机构或个人控制整个虚拟货币系统&#xff0c…

Make All Equal

给定一个循环数组 a1,a2,…,ana1​,a2​,…,an​。 你可以对 aa 至多执行 n−1n−1 次以下操作&#xff1a; 设 mm 为 aa 的当前大小&#xff0c;你可以选择任何两个相邻的元素&#xff0c;其中前一个不大于后一个&#xff08;特别地&#xff0c;amam​ 和 a1a1​ 是相邻的&a…

任务中心示例及浏览器强制高效下载实践

1. 效果展示 这里的进度展示&#xff0c;可以通过我们之前讲到的Vue3实现类ChatGPT聊天式流式输出(vue-sse实现) SSE技术实现&#xff0c;比如用户点击全量下载时&#xff0c;后台需要将PDF文件打包为ZIP文件&#xff0c;由于量较大&#xff0c;需要展示进度&#xff0c;用户点…

SpringBoot整合Flowable【08】- 前后端如何交互

引子 在第02篇中&#xff0c;我通过 Flowable-UI 绘制了一个简单的绩效流程&#xff0c;并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而&#xff0c;在实际业务场景中&#xff0c;如果要求前端将用户绘制的流程文件发送给后端再进行解析处理&#xff0c;这种…

2025 Java面试大全技术文章大纲

2025 Java面试大全技术文章大纲 基础篇 Java核心语法 数据类型与包装类自动装箱与拆箱原理String、StringBuffer、StringBuilder区别final关键字作用场景 面向对象特性 多态的实现机制抽象类与接口的异同设计模式&#xff1a;单例的七种写法泛型擦除与桥接方法 进阶篇 J…

Python aiohttp 全面指南:异步HTTP客户端/服务器框架

边写代码零食不停口 盼盼麦香鸡味块 、卡乐比&#xff08;Calbee&#xff09;薯条三兄弟 独立小包、好时kisses多口味巧克力糖、老金磨方【黑金系列】黑芝麻丸 边写代码边贴面膜 事业美丽两不误 DR. YS 野森博士【AOUFSE/澳芙雪特证】377专研美白淡斑面膜组合 优惠劵 别光顾写…

归并排序:高效稳定的分治算法

归并排序 归并排序采用分治策略实现稳定排序&#xff0c;其核心思想是将序列递归分解后进行有序合并。 def merge_sort(arr):if len(arr) < 1:return arrmid len(arr) // 2left merge_sort(arr[:mid])right merge_sort(arr[mid:])result []i j 0while i < len(le…

go语言基础|slice入门

slice slice介绍 slice中文叫切片&#xff0c;是go官方提供的一个可变数组&#xff0c;是一个轻量级的数据结构&#xff0c;功能上和c的vector&#xff0c;Java的ArrayList差不多。 slice和数组是有一些区别的&#xff0c;是为了弥补数组的一些不足而诞生的数据结构。最大的…

网络攻防技术九:网络监听技术

文章目录 一、网络监听概述二、网络流量劫持三、数据采集与解析四、网络监听检测与防范1、检测实施监听主机2、防范网络通信被监听 一、网络监听概述 主要解决问题&#xff1a;网络流量劫持、在监听点上采集并分析网络数据。主要涉及网卡数据采集、协议分析技术。 二、网络流量…

Cat.1与Cat.4区别及应用场景

Cat.1 和 Cat.4 都是 LTE&#xff08;4G&#xff09;网络中的终端设备类别&#xff0c;主要区别在于 数据传输速率、复杂度和功耗&#xff0c;这直接影响了它们的应用场景和成本。 以下是它们的主要区别&#xff1a; 数据传输速率 (核心区别)&#xff1a; Cat.1 (Category 1)&…

【后端高阶面经:架构篇】51、搜索引擎架构与排序算法:面试关键知识点全解析

一、搜索引擎核心基石&#xff1a;倒排索引技术深度解析 &#xff08;一&#xff09;倒排索引的本质与构建流程 倒排索引&#xff08;Inverted Index&#xff09;是搜索引擎实现快速检索的核心数据结构&#xff0c;与传统数据库的正向索引&#xff08;文档→关键词&#xff0…

深度学习入门:从零搭建你的第一个神经网络

深度学习入门&#xff1a;从零搭建你的第一个神经网络 系统化学习人工智能网站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目录 深度学习入门&#xff1a;从零搭建你的第一个神经网络摘要引言第一章&#xff1a;神经网络基础原理1.1 神经元…

Hadoop 3.x 伪分布式 8088端口无法访问问题处理

【Hadoop】YARN ResourceManager 启动后 8088 端口无法访问问题排查与解决(伪分布式启动Hadoop) 在配置和启动 Hadoop YARN 模块时&#xff0c;发现虽然 ResourceManager 正常启动&#xff0c;JPS 进程中也显示无误&#xff0c;但通过浏览器访问 http://主机IP:8088 时却无法打…

docker B站学习

镜像是一个只读的模板&#xff0c;用来创建容器 容器是docker的运行实例&#xff0c;提供了独立可移植的环境 https://www.bilibili.com/video/BV11L411g7U1?spm_id_from333.788.videopod.episodes&vd_sourcee60c804914459274157197c4388a4d2f&p3 目录挂载 尚硅谷doc…

鸿蒙OSUniApp微服务架构实践:从设计到鸿蒙部署#三方框架 #Uniapp

UniApp微服务架构实践&#xff1a;从设计到鸿蒙部署 引言 在最近的一个大型跨平台项目中&#xff0c;我们面临着一个有趣的挑战&#xff1a;如何在UniApp框架下构建一个可扩展的微服务架构&#xff0c;并确保其在包括鸿蒙在内的多个操作系统上流畅运行。本文将分享我们的实践…

Freemarker快速入门

Freemarker概述 FreeMarker 是一款 模板引擎&#xff1a; 即一种基于模板和要改变的数据&#xff0c; 并用来生成输出文本(HTML网页&#xff0c;电子邮件&#xff0c;配置文件&#xff0c;源代码等)的通用工具。 它不是面向最终用户的&#xff0c;而是一个Java类库&#xff0c…

操作系统:生态思政

操作系统&#xff1a;生态思政 操作系统&#xff08;OS&#xff09;作为数字世界的基石&#xff0c;其意义远超单纯的技术平台。它构建了一个包含开发者、用户、硬件厂商在内的复杂生态系统&#xff0c;其设计理念、运行规则与生态治理模式&#xff0c;无不深刻映射着特定的价…

二进制安全-OpenWrt-uBus

1 需求 需求&#xff1a;ubus list 需求&#xff1a;ubus -v list 需求&#xff1a;ubus -v list zwrt_router.api 2 接口 rootOpenWrt:/# ubus Usage: ubus [<options>] <command> [arguments...] Options:-s <socket>: Set the unix domain …

Rust 学习笔记:自定义构建和发布配置

Rust 学习笔记&#xff1a;自定义构建和发布配置 Rust 学习笔记&#xff1a;自定义构建和发布配置发布配置文件自定义 profile 的选项 Rust 学习笔记&#xff1a;自定义构建和发布配置 发布配置文件 在 Rust 中&#xff0c;发布配置文件是预定义的和可定制的概要文件&#xf…