👉重新谈论文件👈
空文件也要占据磁盘空间。
文件等于文件内容加文件属性。
文件操作等于对文件内容的操作、对文件属性的操作或对文件内容和属性的操作。
标识一个文件,必须使用文件路径加文件名(具有唯一性)。
如果没有指明对应的文件路径,默认是在当前路径(进程的工作路径)进行文件访问。
当我们把 fopen、fclose、fread 和 fwrite 等接口写完之后,代码编译链接形成可执行程序之后,没运行可执行程序,文件对应的操作也没有被执行。故对文件的操作,本质是进程对文件的操作。
一个文件如果没有被打开,不可以进行文件访问。一个文件要被访问,就必须先被用户进程和操作系统打开。
并不是所有的磁盘文件都被打开了,磁盘文件可分为两种:被打开的文件和没有被打开的文件。所以,文件操作的本质是研究进程和被打开文件的关系。(注:文件系统里研究没有被打开的文件)
👉回顾 C 语言的文件操作👈
C 语言有文件操作,C++ 也有文件操作,任何一门语言都会有文件操作,而这些语言的操作接口都不一样!因为这些语言的文件操作接口都不一样,学习的成本是挺高的。那如何降低学习成本呢?我们知道:文件是在磁盘里的,磁盘是硬件。所有人想访问磁盘就不能绕过操作系统,那么开发者就必须使用操作系统提供的文件级别的系统调用接口。所以无论上层语言如何变化,库函数底层实现都必须调用系统调用接口。那么库函数可以千变万化,但是底层是不变的。那我们学习不变的东西,就可以降低学习成本了。
现在我们来回顾一下 C 语言的文件操作接口,再来学习文件操作的系统调用接口。
以 w 的方式打开文件
注:fprintf 函数可以将格式化的数据写到指定的流中。以 w 的方式打开文件,文件不存在会自动创建;如果文件存在,先清空文件的内容再进行写入。
#include <stdio.h> #define FILE_NAME "log.txt" int main() { // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建) // a(append,追加), a+(追加式写入) FILE* fp = fopen(FILE_NAME, "w"); // 没有指明路径,默认在当前路径进行文件操作 if(fp == NULL) { perror("fopen"); return 1; } int cnt = 5; while(cnt) { fprintf(fp,"%s:%d\n", "hello world", cnt--); } return 0; }
以 r 的方式打开文件
#include <stdio.h> #include <string.h> #define FILE_NAME "log.txt" int main() { // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建) // a(append,追加), a+(追加式写入) FILE* fp = fopen(FILE_NAME, "r"); // 没有指明路径,默认在当前路径进行文件操作 if(fp == NULL) { perror("fopen"); return 1; } char buffer[64]; // 读取sizeof(buffer)-1个字符,为\0留一个位置 while(fgets(buffer, sizeof(buffer) - 1, fp) != NULL) { buffer[strlen(buffer) - 1] = '\0'; // 清除\n puts(buffer); } return 0; }
以 a 的方式打开文件
#include <stdio.h> #define FILE_NAME "log.txt" int main() { // r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建) // a(append,追加), a+(追加式写入) FILE* fp = fopen(FILE_NAME, "a"); // 没有指明路径,默认在当前路径进行文件操作 if(fp == NULL) { perror("fopen"); return 1; } int cnt = 5; while(cnt) { fprintf(fp,"%s:%d\n", "hello world", cnt--); } return 0; }
打开文件的方式
如上就是我们之前学的文件相关操作。还有 fseek、ftell 和 rewind 等函数,在 C 语言部分已经学习过,大家可以自行复习一下。
👉文件操作的系统调用👈
open
上面所使用的的 C 语言文件操作函数都是通过调用相应的系统调用接口的,fopen 函数对应的是系统调用 open,fwrite 等函数对应的是系统调用 write,fclose 函数对应的是系统调用 close。
系统调用 open 的参数和返回值
第一个参数:文件路径+文件名。只提供文件名,默认在当前路径进行文件操作。
第二个参数:打开文件的方式。该参数是通过宏来表示不同的打开方式,如:O_RDONLY 只读方式打开文件,O_WRONLY 只写方式打开文件等。通过按位或可以实现不同的文件打开方式,原因是这些宏都是通过比特位的不同来标记不同的选项,也就是说一个比特位就是一个选项。需要注意的是,比特位的位置不能重复。
第三个参数:创建文件的起始权限权限。为打开的文件设置不同的权限。使用 C 语言文件操作函数创建出来的文件默认权限是 664,文件的权限等于起始权限 & (~umask),普通文件的起始权限是 666,目录文件的起始权限是 777。
成功打开文件时返回一个大于 0 的文件描述符,打开失败则返回 -1 并且设置错误码 errno。
O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读,写打开 这三个常量,必须指定一个且只能指定一个 O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限 O_APPEND: 追加写
通过比特位来传递信息示例:
#include <stdio.h> // 不同的标记位表示不同的选项 // 下面的每个宏对应的数值,只有一个比特位是1,彼此的位置不重叠 #define ONE (1<<0) #define TWO (1<<1) #define THREE (1<<2) #define FOUR (1<<3) void show(int flags) { if(flags & ONE) printf("ONE\n"); if(flags & TWO) printf("TWO\n"); if(flags & THREE) printf("THREE\n"); if(flags & FOUR) printf("FOUR\n"); } int main() { show(ONE); printf("--------------------\n"); show(TWO); printf("--------------------\n"); show(ONE | TWO); printf("--------------------\n"); show(ONE | TWO | THREE); printf("--------------------\n"); show(ONE | TWO | THREE | FOUR); return 0; }
那么,系统调用 open 中的各种宏也是通过这种方式实现的。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> #include <unistd.h> #define FILE_NAME "log.txt" int main() { int fd = open(FILE_NAME, O_WRONLY); // assert(fd != -1); if(fd < 0) { perror("open"); return 1; } close(fd); // close关闭文件,参数是对应的文件描述符 return 0; }
宏 O_WRONLY 只是写,没有对应的文件就打开失败,并不是没有对应的文件就自动创建。如果想要没有对应文件就自动创建,想要按位或上 O_CREAT。
现在虽然没有出错,但是创建出来的文件的权限却是全乱的。如果想要创建出来的文件的权限是不乱的,就需要传入 open 的第三个参数。
如果我们不想要系统默认的权限掩码,可以通过 umask 函数来设置。
write
打印被打开文件的文件描述符
关于为什么被打开文件的文件描述符是 3,会在后面的内容里讲解,这也是埋下的一个小小的伏笔。
提示内容:在语言层面上,文件是分文本类文件和二进制类文件的;但再操作系统层面上,文件都是二进制的。
#include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <assert.h> #include <unistd.h> #define FILE_NAME "log.txt" int main() { umask(0); // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask) int fd = open(FILE_NAME, O_WRONLY | O_CREAT, 0666); if(fd < 0) { perror("open"); return 1; } int cnt = 5; char outBuffer[64]; while(cnt) { sprintf(outBuffer, "%s:%d\n", "hello world", cnt--); // C语言规定以\0作为字符串的结尾, 但与文件没有任何的关系, 所以下面的strlen不需要+1 write(fd, outBuffer, strlen(outBuffer)); } //printf("fd:%d\n", fd); close(fd); // close关闭文件,参数是对应的文件描述符 return 0; }
如果我们上面的代码改成下面的样子,再运行起来并查看文件会出现上面情况呢?
可以看到:write 是覆盖式写入的,并不是先清空文件里的内容再进行写入。但是 C 语言的写入不是这样子的呀,C 语言的文件操作函数是对系统调用的封装。如果我们也想要实现文件存在时,先清空文件的内容再进行写入的话,还需要给 open 多传入一个宏 O_TRUNC。
所以,C语言的fopen(FILE_NAME, "w")对应的系统调用就是open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666)。如果想以追加的方式向文件写入的话,只需要再给 open 再传入一个宏 O_APPEND。注:O_APPEND 不要和 O_TRUNC 一起使用。