目录
问题引入
浅谈文件原理
文件操作
文件的打开与关闭
open
close
write与read
再谈C库文件操作
问题引入
🌸以前我们学过C语言的文件操作,而不同语言的文件操作都是不一样的,我们该如何理解这一现象,能不能用一种统一的视角看待所有文件操作?今天就一起来谈谈文件操作。
浅谈文件原理
我们都知道,文件 = 内容 + 属性,对于一个文件的操作可以看成对内容或属性的操作。
通过冯诺依曼体系我们知道当我们对文件进行操作时,该文件一定处于内存之中,而未被进行操作的文件则位于磁盘里。
在操作系统看来,文件便可以被分成两大类:磁盘文件和被打开文件。
🌸其中被打开文件至少需要将文件属性加载到内存之中,而每次文件操作前我们都要先打开文件的本质就是:将目标文件属性加载到内存中。因此,OS内部一定会同时存在大量的被打开的文件。需要将其以先描述再组织的方式管理起来。
这样在OS内部对于打开文件的管理就转换成了对链表的增删查改。即文件被打开前,OS要为被打开文件创建内核数据结构 struct file。
🌸那么文件是被谁打开的呢?
- 用户通过调用进程完成打开文件的操作
如此解释后,我们对于文件又有进一步的认识,下面我们来介绍一下在OS层面文件操作的系统接口。
文件操作
🌸对C语言文件操作不熟悉或有些忘记了的,也可以看看这篇复习一下:C语言文件操作的基础应用。
文件的打开与关闭
open
🌸通过手册查询,我们可以找到 open 函数的相关信息,其中的参数分别是文件路径和打开文件的选项。
🌸其中的 flag ,我们不能将其看成一个整数而是将其看作一个位图。
🌸其中每一个位置都代表一个选项,为 1 则表示选中反之不选,库中定义了宏方便我们直接使用。
- O_WRONLY 只写选项
- O_RDONLY 只读选项
- O_APPEND 追加写入
- O_TRUNC 打开时清空文件
- O_CREAT 若文件不存在则创建
- O_RDWR 支持同时读写
🌸在使用时使用 | 操作符标记位图的对应位置即可。
int main()
{
open("log.txt", O_WRONLY | O_TRUNC | O_CREAT); //以只读每次打开清空文件若不存在则创建新文件的方式打开
return 0;
}
🌸这时候,我们便会发现现在的这个操作跟 fopen 十分的相似,只不过将 w 选项拆成了三个选项。其实,fopen 的底层便是调用了这个系统调用进行文件的打开操作。
🌸虽然我们成功创建出一个文件了,但此时我们可以看到,该文件的权限却是乱码,因此在创建文件时我们就需要手动设置文件的权限。
🌸open 还重载了三个参数的版本,供我们设置文件权限。
int main()
{
open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
return 0;
}
🌸这下创建出来的就是正常权限的文件了,但是却与我们设置的 0666 不一样而是 0664 呢?这是由于文件的权限还受到权限掩码的影响,因此需要将 umask 置零。
int main()
{
umask(0);
open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
return 0;
}
🌸这下就根据我们要求创建出对应权限的文件了。
🌸而 open 有一个返回值,我们刚才忽略掉了,其为文件描述符又名 fd,我们需要定义一个变量接收一下它,之后需要用它定位文件。
🌸若 fd 为 -1 说明打开文件失败,我们可以在打开文件后增加一个判断。
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
if(fd == -1)
{
printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
}
return 0;
}
close
🌸关闭文件就需要我们上面存起来的 fd 了,直接传入 fd 即可关闭文件。
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
if(fd == -1)
{
printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
}
close(fd);
return 0;
}
write与read
🌸打开文件后我们就可以根据需求对文件进行读写了,先介绍 write 接口。
🌸参数分别是 fd,写入内容的字符串和写入的字节数,写入后返回写入的字节数,失败则返回 -1。
🌸这里我先将用 snprintf 内容格式化写入一个字符串中,再写到文件里。
int main()
{
umask(0);
int fd = open("log.txt", O_WRONLY | O_TRUNC | O_CREAT,0666);
if(fd == -1)
{
printf("fd: %d,errstring is: %s\n",fd,strerror(errno));
}
const char* ptr = "helloworld"; //原字符串
char str[20]; //转接的字符数组
int i = 1;
snprintf(str,sizeof(str),"%s: %d",ptr,i); //加个序号格式化写入
write(fd,str,strlen(str)); //写入文件
close(fd); //关闭文件
return 0;
}
[注意]:\0 是C语言库中的设定,直接写入到文件中会被识别成乱码,使用 write 写入时要注意不写入 \0。因此写入时直接使用 strlen 便可以自动规避掉 \0。
🌸之后我们来看读取的操作,需要传入 fd 定位文件,之后将文件内的数据读入数组中,读取 count 个字节。
🌸成功读取后会返回读取的字节数,若读取失败则会返回 -1。
🌸这次我们以读取的方式打开文件,由于不用创建文件因此就不需要手动设置文件权限了。
int main()
{
int fd = open("log.txt", O_RDONLY); //只读打开文件
char buffer[1024];
size_t n = read(fd, buffer, sizeof(buffer) - 1); //为\0留一个位置
if (n > 0) //说明读入数据
{
buffer[n] = '\0'; //在数据尾部插入\0
printf("%s\n", buffer); //打印出数据内容
}
return 0;
}
🌸同时为了方便接下来的读写,读取需要为 \0 预留一个位置,之后再加上即可。
再谈C库文件操作
🌸在上面我们就说过C库文件操作的底层本质上也是调用了操作系统提供的系统调用,即库函数只是对系统调用的封装。
🌸那又是为什么呢?我们可以用下面这张图简单理清几者之间的关系。
🌸只要涉及文件操作,就与文件读写分不开关系,在读写的过程中便会涉及到对硬件的访问,而硬件是由操作系统进行管理的。因此从用户层面出发,只要访问到硬件就无法绕开操作系统。
🌸不仅只是C语言,任何语言的文件操作都是对系统调用的封装,都是借助系统接口才能够实现!!!
🌸好了,今天 浅谈文件原理与操作 的相关内容到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。