文件操作详解

    科技2022-07-10  184

    C语言(标准库IO接口)

    打开、关闭文件

    path:文件路径名mode r:只读 r+:可读可写 w:只写,文件不存在则创建,若文件存在则清空原有内容 w+:相较于w,多出了可读操作 a:追加写打开,若文件不存在则创建,每次总是写到文件末尾 a+:相较于a,多出了可读操作返回值:文件的操作句柄(文件流指针)

    fp:文件的操作句柄

    读写文件

    ptr:用于接收数据的缓冲区size:一次要读取的字节大小nmemb:一次要读取多少个 sizestream:fopen返回的文件流指针返回值:成功读取到的字节大小    若读取到文件末尾:返回0

    ptr:要写入的数据size:一次要写入的字节大小nmemb:一次要写入多少个 sizestream:fopen返回的文件流指针返回值:实际写入的块个数

    文件指针位置重定位

    对文件的读写位置跳转到whence位置偏移offset个字节处stream:文件的操作句柄whence:跳转的起始位置 SEEK_SET:文件起始位置 SEEK_CUR:当前读写位置 SEEK_END:文件末尾位置offset:偏移量

    测试用例

    #include <stdio.h> #include <string.h> int main() { FILE* fp = fopen("./myfile.txt", "r+"); if(!fp) { printf("fopen error\n"); return -1; } const char* str = "hello world\n"; size_t wnum = fwrite(str, 1, strlen(str), fp); if(wnum == 0) { printf("fwrite error\n"); return -1; } printf("wnum:%d\n", (int)wnum); //跳转到文件的起始位置,否则会读取不到数据,因为此时的文件指针在末尾 fseek(fp, 0, SEEK_SET); char buf[1024] = {0}; size_t rnum = fread(buf, 1, sizeof(buf) - 1, fp); printf("rnum:%d --- buf:%s\n", (int)rnum, buf); //关闭文件 fclose(fp); return 0; }

    Linux(系统调用IO接口)

    打开、关闭文件

    pathname:文件路径名flags:选项参数,文件的打开方式,必选项、可选项 必选项(只能选择其一):O_RDONLY—只读    O_WRONLY—只写    O_RDWR—读写 可选项: O_CREAT:若文件存在则打开,否则创建新文件 O_EXCL:与O_CREAT同时使用,若文件存在则报错,不存在则创建新文件 O_TRUNC:打开文件的同时清空原有内容 O_APPEND:追加写,总是将数据写入到文件末尾mode:文件的权限,如果使用了O_CREAT有可能创建新文件,就一定要指定文件权限,八进制数字形式返回值:一个非负整数,文件描述符,文件的操作句柄    错误:-1

    fd:文件的操作句柄

    读写文件

    fd:文件的操作句柄buf:要写入文件的数据的空间首地址count:要写入的数据大小返回值:返回实际写入文件的数据字节长度    错误:-1

    fd:文件的操作句柄buf:从文件中读取数据放到哪块缓冲区中的首地址len:想要读取的数据长度,注意这个len不能大于缓冲区的大小返回值:返回的是实际读取到的数据字节长度    错误:-1

    文件指针位置重定位

    fd:文件的操作句柄offset:偏移量whence:从哪里开始偏移 SEEK_SET:文件的起始位置 SEEK_CUR:文件当前读写位置 SEEK_END:文件末尾返回值:成功返回当前位置相对于起始位置的偏移量    失败:-1

    测试用例

    #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> int main() { umask(0); int fd = open("./myfile.txt", O_RDWR | O_CREAT, 0777); if(fd < 0) { perror("open error"); return -1; } //每次将写位置跳转到文件末尾,相当于追加写 lseek(fd, 0, SEEK_END); const char* str = "hello world\n"; ssize_t ret = write(fd, str, strlen(str)); if(ret < 0) { perror("write error"); return -1; } printf("ret:%d\n", (int)ret); //跳转到文件的起始位置,否则会读取不到数据,因为此时文件指针在末尾 lseek(fd, 0, SEEK_SET); char buf[1024] = {0}; ret = read(fd, buf, sizeof(buf) - 1); if(ret < 0) { perror("read error"); return -1; } printf("ret:%d---buf:%s\n", (int)ret, buf); //关闭文件 close(fd); return 0; }

    文件描述符

    文件描述符:非负整数(内核中文件描述信息结构体数组的下标)

    进程通过 struct file 结构体来描述打开的文件,使用了 struct file *fd_array[]; 文件描述符就是这个数组的下标;用户打开文件,操作系统通过 struct file 结构体描述文件,并且将指针添加进 fd_array 数组中,向用户返回这个文件描述信息在数组中位置(下标),用户操作文件的时候,将这个下标传递给操作系统,操作系统通过下标找到文件描述信息进而操作文件。

    为什么打开一个文件后,如果不操作了一定要关闭,释放资源? 文件描述符实际是有限的,若不关闭文件,文件描述符用完了后,则在进程中就打不开新文件了。

    文件描述符的分配规则:最小未使用

    一个程序运行起来后,进程中默认打开三个文件:标准输入stdin(0),标准输出stdout(1),标准错误stderr(2)

    标准输入标准输出标准错误012文件描述符(int)stdinstdoutstderr文件流指针(FILE*)

    测试用例:

    //这个demo体会关闭1号文件描述符,即标准输出 #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> int main() { //将文件权限掩码设置为0 umask(0); //关闭标准输出文件描述符 close(1); int fd = open("./test.txt", O_RDWR | O_CREAT, 0664); if(fd < 0) { perror("open error"); return -1; } printf("fd = %d\n", fd); fflush(stdout); //关闭文件,释放资源 close(fd); return 0; }

    从上述例子我们可以发现 printf 并非真的一定要把数据写入标准输出文件,而是因为 printf 函数中操作文件的时候操作的描述符是1。原本 向1中写入数据就是向标准输出写入,然后当1指向了 新的文件后,这时候 printf 就会将数据写入到指定的新的文件中。

    重定向 将数据不再写入原本的文件,而是写入新的指定的文件中,实现方式就是替换这个描述符对应的文件描述信息。实际是描述符的重定向,改变描述符所指向的文件,就改变了数据的流向。

    描述符重定向函数: 让 newfd 这个描述符也指向 oldfd 所指向的文件,这时候 oldfd 和 newfd 都能够操作 oldfd 所指向的文件。

    测试用例:

    //这个demo体会关闭1号文件描述符,即标准输出 #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/stat.h> int main() { //将文件权限掩码设置为0 umask(0); int fd = open("./test_dup2.txt", O_RDWR | O_CREAT, 0664); if(fd < 0) { perror("open error"); return -1; } //将1号文件描述符也指向fd所描述的文件 dup2(fd, 1); printf("fd = %d\n", fd); fflush(stdout); //关闭文件,释放资源 close(fd); return 0; }

    文件描述符与文件流指针的关系

    库函数的操作句柄是文件流指针系统调用接口的操作句柄是文件描述符

    库函数是对系统调用接口的一层封装,通过文件流指针进行最终文件操作的时候,依然还要能够找到文件对应的文件描述符才可以 。文件流指针是一个结构体,结构体中有很多成员变量,其中就有一个叫 _fileno ,这就是文件描述符。 库函数接口向文件中写入数据,并不会直接写入文件,而是先写入缓冲区中,刷新缓冲区的时候才会写入文件。 系统调用接口是直接将数据写入文件的,系统调用接口没有这个缓冲区的。只有库函数才存在这个缓冲区。

    测试用例:

    //这个demo体会库函数接口和系统调用接口 缓冲区的有无及输出 #include <stdio.h> #include <unistd.h> #include <fcntl.h> int main() { //库函数接口必须刷新缓冲区后才会将数据打印,程序退出前会刷新缓冲区,所以最后打印 fprintf(stdout, "%s---%d", "fprintf", 1); fwrite("fwrite", 1, 6, stdout); printf("printf"); //系统调用接口直接将数据写入到文件中,所以先打印 write(1, "write", 5); sleep(3); return 0; }

    文件系统

    Linux下的 ext2 文件系统为例。文件系统就是磁盘上管理文件的系统。 存储一个文件需要找到空闲的磁盘块存储文件数据,以及需要找到一个未被使用的 inode 节点存储自己的元信息。 针对每一个磁盘块做一个整体的位图,这样就可以快速找到空闲的磁盘块。

    文件的权限就是使用位图存储,一个用户没有权限也就是 0/1 的关系,位图可以节省大量的空间以及二进制的与或非速度非常快。

    文件的存储过程:通过inode_bitmap在inode区域获取空闲inode节点,通过data_bitmap获取空闲数据块,在inode结点中记录文件信息以及数据块位置,并且将文件数据写入到数据块中,将自己的目录项信息(inode节点号+文件名)添加到所在目录文件中。

    目录文件:一个文件–>文件中记录的目录下的文件信息(文件名+inode节点号)–>目录项

    文件的查找过程:通过目录项信息快速找到文件的inode节点号,通过inode节点号可以在inode table中快速找到文件的inode节点;通过inode节点可以找到文件数据存储位置,进而获取到数据。

    软链接、硬链接文件

    给一个源文件创建一个软链接文件/硬链接文件,就可以通过被创建出来的软链接文件/硬链接文件来操作源文件

    为源文件创建一个硬链接文件:ln failname.txt failname.hard 为源文件创建一个软链接文件:ln -s failname.txt failname.soft

    软链接文件和硬链接文件的区别 : 硬链接文件:本质上和源文件没有什么不同,都是一个文件的名称,与源文件共用同一个 inode节点,通过自己的 inode节点访问源文件数据。 软链接文件:本质上是一个独立的文件,有自己的 inode节点,文件数据中保存源文件的路径,通过这个路径访问源文件的数据。

    删除上的区别: 删除源文件,软链接失效,硬链接文件只是链接数 -1,链接数(一个 inode节点对应有几个目录项) 删除一个文件,文件并不会立即被删除,而是直接删除了目录项信息,inode中的链接数 -1,此后链接数为 0 时,才会真正删除文件

    软链接文件可以跨分区,硬链接文件不可以 软链接文件可以对目录创建,硬链接不可以

    Processed: 0.018, SQL: 8