文件IO
1.系统IO
在裸机开发中,我们的程序直接与硬件打交道,应用直接操作硬件,必须了解硬件的具体实现细节,无并发,不能同时运行多个程序
app <----> hardware
在Linux开发中,程序和OS打交道,开发人员只需要将重心放在业务的实现逻辑上,而不需要考虑硬件的具体实现细节,提供并发,可以同时运行多个程序,占用资源较大
app <----> OS <---->hardware
操纵系统(OS):管理和分配硬件资源的系统软件,其向应用层提供进程管理,内存管理,文件管理,网络管理等服务,而文件IO就是使用文件系统所提供的IO接口进行应用开发,对于系统提供的文件操作的接口我们称之为系统IO
在系统IO中,操作系统给我们提供的接口包括 open
read
write
close
lseek
等。
1.1文件系统
文件系统:用来存储、组织、管理文件的一套方法、方式、协议以及软件实现等
文件包括 文件属性和文件内容
文件属性:i-node
或 inode
,文件存在与否的唯一表示,包括文件大小、文件类型、文件时间......
1.1.1文件的操作过程
硬件上:inode
节点标识相应的文件
Linux内核:会为每一个inode
生成一个struct inode
来描述一个文件的物理inode
信息,一个struct inode
只对应一个文件,多个struct inode
可以指向同一个文件;当应用层打开一个文件的时候,OS 会为被打开的文件创建一个struct file
来描述这个被打开的文件;在struct file
中 会保存 文件状态标记、文件偏移量、struct inode *
;每打开一个文件,肯定会有一个struct file,一个文件可以被不同的进程打开;linux系统为了屏蔽文件操作的具体实现细节,它会为每一个进程创建一个进程文件表项,用来保存每个进程所打开的文件struct file*
,应用层最终的到的用来描述文件的数据只是进程文件表项中的某一项(下标)
APP-> fd -> struct file*FD[] -> struct file -> struct inode -> inode ->文件内容
在应用层我们只需要关心如何得到这个文件描述符-fd,以及如何操作这个fd,操作系统会为我们提供一系列的接口去操作这个fd,这些接口我们称之为 系统IO
1.2Linux系统IO
1.2.1 open
//功能:打开/创建一个文件 //头文件 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> //声明 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); /** *pathname:路径名 带路径的文件名 *flags:打开方式 * O_RDONLY 只读 * O_WRONLY 只写 * O_RDWR 读写 * O_CREAT 创建,如果文件不存在就创建文件 * O_APPEND 追加,文件打开后,光标/偏移量在文件尾 * O_TRUNC 截断,打开时清空文件 * O_EXCL 测试文件是否存在 * O_NONBLOCK 非阻塞方式打开文件 * *mode:指定文件创建时的权限,如果flags没有指定O_CREAT,这个参数就没有意义 * S_IRWXU 00700 user用户可读可写可执行 * S_IRUSR 00400 user用户可读 * S_IWUSR 00200 user用户可写 * S_IXUSR 00100 user用户可执行 * S_IRWXG 00070 group用户可读可写可执行 * S_IRGRP 00040 group用户可读 * S_IWGRP 00020 group用户可写 * S_IXGRP 00010 group用户可执行 * S_IRWXO 00007 其他用户可读可写可执行 * S_IROTH 00004 其他用户可读 * S_IWOTH 00002 其他用户可写 * S_IXOTH 00001 其他用户可执行 * *返回值: * 成功,返回一个文件描述符 >2 * 失败,返回-1,并且errno被设置 */
- 对于阻塞状态,如果一个文件没有内容,那么
read
会阻塞,直到这个空间有内容;如果这个文件没有空间了,那么write
就会阻塞,直到这个文件有空间 - 对于非阻塞状态,如果一个文件没有内容,那么
read
会直接返回一个错误;如果这个文件没有空间了,那么write
就会直接返回一个错误 - 操作系统会为每一个进程自动打开三个文件,标准输入文件
STDIN_FILENO 0
标准输出文件STDOUT_FILENO 1
标准出错文件STDERR_FILENO 2
errno
错误编号:不同的错误编号代表不同的错误原因,可以使用perror
函数输出当前错误号所对应的错误原因
1.2.2 read
//功能:从一个文件中读取数据 //头文件 #include <unistd.h> //声明 ssize_t read(int fd, void *buf, size_t count); /** *fd:要读取的文件的文件描述符 *buf:保存数据的地址 *count:读取数据的字节数 *返回值: * 成功:返回实际读取到的字节数 * 失败:返回-1,并设置errno */
1.2.3 write
//功能:向一个文件中写入数据 //头文件 #include <unistd.h> //声明 ssize_t write(int fd, const void *buf, size_t count); /** *fd:要写入的文件的文件描述符 *buf:保存数据的地址 *count:写入数据的字节数 *返回值: * 成功:返回实际写入到的字节数 * 失败:返回-1,并设置errno */
1.2.4 close
//功能:关闭一个文件 //头文件 #include <unistd.h> //声明 int close(int fd); /** *fd:要关闭的文件的文件描述符 *返回值: * 成功:返回0 * 失败:返回-1,并设置errno */
1.2.5 lseek
文件偏移量:标识文件读写的位置
在read/write
的过程中,当我们第一次打开文件时,文件偏移量默认在开头,当read/write
10个字节后,文件偏移量会相应的移动10个字节。
//功能:重定位文件偏移量 //头文件 #include <sys/types.h> #include <unistd.h> //声明 off_t lseek(int fd, off_t offset, int whence); /** *fd:文件描述符 *offset:偏移量 * 为正表示向后偏移,为负表示向前偏移,单位是字节 *whence:偏移开始的位置 * SEEK_SET 文件头 * SEEK_CUR 当前 * SEEK_END 文件尾 *返回值: * 成功:返回距离文件头的偏移量 * 失败:返回-1,并设置errno */
1.2.6 umask
//功能:设置一个文件在创建时的掩码 //头文件 #include <sys/types.h> #include <sys/stat.h> //声明 mode_t umask(mode_t mask); /** *mask:指定文件掩码 *返回值: * 返回原先的文件掩码 */
1.2.7获取当前的工作目录
getcwd
getwd
get_current_dir_name
//头文件 #include <unistd.h> //声明 char *getcwd(char *buf, size_t size); /** *buf:保存当前获取到的进程工作目录,以字符串形式 *size:buf空间的最大长度,如果当前工作目录的字符串长度大于size-1,函数就会报错 *返回值 * 成功:返回执行工作目录的指针 * 失败:返回NULL,并设置errno */ char *getwd(char *buf); /** *buf:保存当前获取到的进程工作目录,以字符串形式 *返回值 * 成功:返回执行工作目录的指针 * 失败:返回NULL,并设置errno */ char *get_current_dir_name(void);//内部使用了malloc分配的空间,要free /** *返回值 * 成功:返回执行工作目录的指针 * 失败:返回NULL,并设置errno *使用该函数前,需要定义一个宏 _GNU_SOURCE */
1.2.8改变进程当前的工作目录
chdir
fchdir
//头文件 #include <unistd.h> //声明 int chdir(const char *path); /** *path:要指定的工作路径 *返回值: * 成功:返回0 * 失败:返回-1,并设置errno */ int fchdir(int fd); /** *fd:要指定的工作目录的文件描述符 *返回值: * 成功:返回0 * 失败:返回-1,并设置errno */
1.2.9文件截短
truncate
ftruncate
//功能:截短一个文件到固定的长度 //头文件 #include <unistd.h> #include <sys/types.h> //声明 int truncate(const char *path, off_t length); /** *path:文件路径 *length:截短长度 *返回值: * 成功返回0 * 失败返回-1,并设置errno */ int ftruncate(int fd, off_t length); /** *path:文件描述符 *length:截短长度 *返回值: * 成功返回0 * 失败返回-1,并设置errno */
- 如果文件的大小大于lenth,截短
- 如果文件的大小小于lenth,文件大小会变大为lenth
1.2.10删除文件
unlink
删除一个文件
rmdir
删除一个目录
//头文件 #include <unistd.h> //声明 int unlink(const char *pathname); /** *pathname:文件路径 *返回值: * 成功返回0 * 失败返回-1,并设置errno */ int rmdir(const char *pathname); /** *pathname:文件路径 *返回值: * 成功返回0 * 失败返回-1,并设置errno */
unlink
调用时如果有进程打开了这个文件,并不会直接删除inode
,而是做一个删除标记,这个inode
将会在使用完毕后删除
remove
删除一个文件/空目录
//头文件 #include <stdio.h> //声明 int remove(const char *pathname); /** *pathname:文件路径 *返回值: * 成功返回0 * 失败返回-1,并设置errno */
1.2.11 文件属性
stat
fstat
lstat
//头文件 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> //声明 int stat(const char *pathname, struct stat *statbuf); /** *pathname:文件路径 *statbuf:保存获得的文件属性 *返回值: * 成功返回0 * 失败返回-1,并设置errno */ int fstat(int fd, struct stat *statbuf); /** *fd:文件描述符 *statbuf:保存获得的文件属性 *返回值: * 成功返回0 * 失败返回-1,并设置errno */ int lstat(const char *pathname, struct stat *statbuf); /** *pathname:文件路径 *statbuf:保存获得的文件属性 *返回值: * 成功返回0 * 失败返回-1,并设置errno */
-
如果pathname对应的是一个符号链接,
lstat
获取的文件是符号链接本身,而stat
获取的是符号链接所指向的文件 -
struct stat
struct stat { dev_t st_dev; //设备号 ino_t st_ino; //节点号 mode_t st_mode; //文件类型和权限 nlink_t st_nlink; //文件的硬链接数,不能跨文件系统 uid_t st_uid; //用户ID gid_t st_gid; //组用户ID dev_t st_rdev; //设备号 off_t st_size; //文件大小 blksize_t st_blksize; //块大小 blkcnt_t st_blocks; //文件所占块数 struct timespec st_atim; //最后一次访问该文件的时间 struct timespec st_mtim; //最后一次修改该文件内容的时间 struct timespec st_ctim; //最后一次修改该文件属性的时间 #define st_atime st_atim.tv_sec /* Backward compatibility */ #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec }; -
判断文件的类型
S_ISREG(*.st_mode); //为真,表示是一个普通文件 - S_ISDIR(*.st_mode); //为真,表示是一个目录 d S_ISCHR(*.st_mode); //为真,表示是一个字符设备 c S_ISBLK(*.st_mode); //为真,表示是一个块文件 b S_ISFIFO(*.st_mode); //为真,表示是一个管道文件 p S_ISLNK(*.st_mode); //为真,表示是一个符号链接文件 l S_ISSOCK(*.st_mode); //为真,表示是一个套接字文件 s -
判断文件的权限
*.st_mode & S_IRUSR //为真,说明owner 用户具备可读的权限 *.st_mode & S_IWUSR //为真,说明owner 用户具备可读的权限 ......
1.3目录
目录和文件一样,都有inode
,在创建一个目录的时候,系统会为它预留一块空间——目录项数组空间,往其中添加一个文件,就会往目录项数组中新增一个目录项。
1.3.1目录操作
opendir
打开一个目录
//头文件 #include <sys/types.h> #include <dirent.h> //声明 DIR *opendir(const char *name); /** *name:都路径的目录名 *返回值: * 成功,返回一个目录指针 * 失败,返回NULL,并设置errno */
readdir
//头文件 #include <dirent.h> //声明 struct dirent *readdir(DIR *dirp); /** *dirp:目录指针 *返回值: * 成功,返回一个目录项指针 * 失败,返回NULL,并设置errno */
-
struct dirent
struct dirent { ino_t d_ino; /* Inode number */ off_t d_off; /* Not an offset; see below */ unsigned short d_reclen; /* Length of this record */ unsigned char d_type; /* Type of file; not supported by all filesystem types */ char d_name[256]; /* Null-terminated filename*/ };
closedir
//头文件 #include <sys/types.h> #include <dirent.h> //声明 int closedir(DIR *dirp); /** *dirp:目录指针 *返回值: * 成功,返回0 * 失败,返回-1,并设置errno */
2.标准IO
每个操作系统都提供了一套基于自身操作系统的IO接口---系统IO,每个操作系统对文件的管理和接口都是不一样的。
C标准委员会,制定了一套统一的文件接口---C标准IO库,主要统一对文件常用的接口的操作。
C标准IO库可以操作的文件:普通文本文件以及二进制文件
文本文件:无组织、无格式的文件,以字符的编码来解析,如.txt
.c
.h
.s
......
二进制文件:有特定格式的文件,如.jpg
.gif
.mp3
......
在标准IO库,用 FILE 结构体来描述或者表示一个文件的,这个结构体内部有两个缓冲区,一个读缓冲,一个是写缓冲。
同步问题:缓冲区的数据什么时候同步到外设上
缓冲区有三种类型:
行缓冲:缓冲区的数据达到一行(如果数据中有换行符也表示一行结束),同步到外设中
全缓冲:缓冲区的数据要填满整个缓冲区,才同步到外设中去
无缓冲:缓冲区中有一个字节,就同步到外设上
标准IO库,会自动为每个进程,打开三个标准IO流:标准输入流 : FILE * stdin,标准输出流 : FILE * stdout,标准出错流 : FILE * stderr
2.1标准IO的函数接口
2.1.1打开和关闭
fopen
fclose
//头文件 #include <stdio.h> //声明 FILE *fopen(const char *pathname, const char *mode); /** *pathname:要打开的文件 *mode:打开文件的方式 * "r" 只读打开,文件不存在报错,打开后光标在开头 * "r+" 读写打开,文件不存在报错,打开后光标在开头 * "w" 只写打开,文件不存在则创建,打开后文件内容截短/清空 * "w+" 读写打开,文件不存在则创建,打开后文件内容截短/清空 * "a" 追加打开,文件不存在则创建,打开后光标在末尾,文件不会被截短 * "a+" 读写打开,文件不存在则创建,原始读的位置在开头,原始写的位置在结尾 *返回值: * 成功,返回一个文件指针 * 失败,返回NULL,并设置errno */ int fclose(FILE *stream); /** *stream:要关闭的文件 *返回值: * 成功,返回0 * 失败返回-1,并设置errno */
2.1.2读写流
每次读写一个字符
//头文件 #include <stdio.h> //声明 int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void); int fputc(int c, FILE *stream); int putc(int c, FILE *stream); int putchar(int c);
每次读写一行
//头文件 #include <stdio.h> //声明 char *gets(char *s); char *fgets(char *s, int size, FILE *stream); int puts(const char *s); int fputs(const char *s, FILE *stream);
直接读写
fread
fwrite
//头文件 #include <stdio.h> //声明 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); /** *ptr:指向用来存储的空间 *size:每个元素所占的字节数 *nmemb:要读取的元素的个数 *stream:要读取的文件 *返回值: * 成功,返回实际读取到的元素的个数 * 失败,返回-1,并设置errno */ size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); /** *ptr:指向用来存储的空间 *size:每个元素所占的字节数 *nmemb:要写入的元素的个数 *stream:要写入的文件 *返回值: * 成功,返回实际写入的元素的个数 * 失败,返回-1,并设置errno */
2.1.3冲洗流
fflush
同步
//头文件 #include <stdio.h> //声明 int fflush(FILE *stream); /** *stream:要冲洗的流文件 *返回值: * 成功,返回0 * 失败,返回-1,并设置errno *对于输出流,该函数将写缓冲的数据,更新到外设中 *对于输入流,该函数将都缓冲的数据,直接丢弃 *对于NULL,该函数将进程打开的所有输出文件流进行同步 */
2.1.4定位流
fseek
ftell
rewind
//头文件 #include <stdio.h> //声明 int fseek(FILE *stream, long offset, int whence); /** *stream:要重新定位的流文件 *offset:偏移量,左负右正 *whence:偏移起始位置 * SEEK_SET * SEEK_CUR * SEEK_END *返回值: * 成功,返回0 * 失败,返回-1,并设置errno */ long ftell(FILE *stream); /** *stream:指向流文件 *返回值: * 成功,返回光标距离文件头的字节数 * 失败,返回-1,并设置errno */ void rewind(FILE *stream);//将光标定位到文件头
2.1.5文件出错/结束标志
EOF
文件结束标志
//头文件 #include <stdio.h> //声明 int feof(FILE *stream); /** *stream:指向流文件 *返回值: * 文件到达末尾,返回非0 * 文件没有到达末尾,返回0 */
2.1.6格式化IO
格式化输入 scanf
sscanf
fscanf
//头文件 #include <stdio.h> //声明 int scanf(const char *format, ...); /** *format:格式化字符串,指定用户输入的格式 *...:地址列表 *返回值:成功匹配的变量个数 */ int fscanf(FILE *stream, const char *format, ...); /** *stream:输入文件流 *format:格式化字符串,指定用户输入的格式 *...:地址列表 *返回值:成功匹配的变量个数 */ int sscanf(const char *str, const char *format, ...); /** *str:从str指定的字符串中输入 *format:格式化字符串,指定用户输入的格式 *...:地址列表 *返回值:成功匹配的变量个数 */
格式化输出 printf
sprintf
fprintf
//头文件 #include <stdio.h> //声明 int printf(const char *format, ...); /** *format:格式化字符串 *...:对象列表 *返回值:实际打印的字符个数 */ int fprintf(FILE *stream, const char *format, ...); /** *stream:输出文件流 *format:格式化字符串 *...:对象列表 *返回值:实际打印的字符个数 */ int sprintf(char *str, const char *format, ...); /** *str:输出到str指定的空间 *format:格式化字符串 *...:对象列表 *返回值:实际打印的字符个数 */ int snprintf(char *str, size_t size, const char *format, ...); /** *str:输出到str指定的空间 *size:指定长度 *format:格式化字符串 *...:对象列表 *返回值:实际打印的字符个数 */
本文作者:乐情在水静气同山
本文链接:https://www.cnblogs.com/aalynsah/p/17639710.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探