Linux 文件操作接口
在使用语言编写服务的时候不乏会遇到需要对文件进行操作的场景,Linux内核是用C语言写的,了解Linux之前先熟悉一下C语言文件操作接口,方便对比。
C语言文件操作接口
C语言文件描述
#ifndef _FILE_DEFINEDstruct _iobuf { char *_ptr; //文件输入的下一个位置 int _cnt; //当前缓冲区的相对位置 char *_base; //指基础位置(即是文件的起始位置) int _flag; //文件标志 int _file; //文件描述符id int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取 int _bufsiz; //文件缓冲区大小 char *_tmpfname; //临时文件名 };typedef struct _iobuf FILE;#define _FILE_DEFINED#endif
C语言对文件操作需要的数据都存在这样的数据结构里,C语言对文件操作时,用一个数据结构唯一标识一个文件流
fopen()
FILE* fopen(const char *path, const char *mode);
返回值为文件流结构体指针,当打开失败时返回NULL 指针。
对文件操作前需先打开文件,打开文件使用接口fopen()。
参数:
path: 文件路径,可以是相对路径也可以是绝对路径(默认为进程打开时路径)
mode: 打开方式
模式 | 含义 | 文件不存在时 |
r | 只读 | 报错 |
w | 只写 | 创建文件 |
a | 追加只写 | 创建文件 |
rb | 二进制只读 | 报错 |
wb | 二进制只写 | 创建文件 |
ab | 二进制追加只写 | 创建文件 |
r+ | 读写 | 报错 |
w+ | 读写 | 创建文件 |
a+ | 追加读写 | 创建文件 |
rb+ | 二进制读写 | 报错 |
wb+ | 二进制读写 | 创建文件 |
ab+ | 二进制追加读写 | 创建文件 |
以上打开模式凡带**'b'** 操作的打开文件时都会清空文件。
r模式打开文件
![](https://i-blog.csdnimg.cn/blog_migrate/d3dd68675dd6517cfbbec1193579ddd3.png)
)
文件不存在时打开失败
![](https://i-blog.csdnimg.cn/blog_migrate/9244f4d6445a83f9ca9b033e5b775b51.png)
文件存在时打开成功
w模式打开文件
![](https://i-blog.csdnimg.cn/blog_migrate/d4f692e64f002f55d79cf1a4e410f2f8.png)
自动创建需要打开的文件
![](https://i-blog.csdnimg.cn/blog_migrate/44ba7a0f009e56ae0cccec476abc3550.png)
打开后会清空文件
a模式打开文件
![](https://i-blog.csdnimg.cn/blog_migrate/5226efb244d115b07254b505753942fc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ca2051bb51c88c444e1c7e6858ff455d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ea44100e863951b7d95e125af2de1da9.png)
以a模式打开文件不存在时创建文件,存在时在文件末尾写入内容。
其他模式类似
fclose()
C语言程序员要养成用完即释放的好习惯尽量避免内存泄漏,fclose() 接口就是用来关闭文件流的。
int fclose(FILE *fp);
关闭接口参数简单,只需将需要关闭的文件流指针传入即可。
fwrite()
size_t fwrite(const void *ptr, size_t size, size_t number, FILE *stream);
参数:
ptr: 写入文件的内容
size: 写入单位数据大小(byte)
number: 写入数据总数
stream: 文件流
![](https://i-blog.csdnimg.cn/blog_migrate/61841033a5efe7ac3fa94c6879314287.png)
写入操作传参如图
fread()
size_t fread(void *ptr, size_t size, size_t number, FILE *stream);
参数类似fwrite()
ptr 为要读入文件内容的容器,必须提前开好空间,number 不得大于实际开好的空间
![](https://i-blog.csdnimg.cn/blog_migrate/f54df662051c09e336cb5f01761be6e4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f65d5a3edea66d1ff2312c0e01badbd3.jpeg)
读文件操作如图
系统文件操作接口
文件描述符fd
文件描述符可以唯一标识该进程打开的流。
open()
int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);
参数:
pathname: 文件路径,默认为进程创建时的路径
flags: 决定文件打开模式
mode: 创建文件时文件权限
理解标记位原理
计算机中所有数据都由比特位组成,一个整形有32个比特位,用一位作为标记,一个int 类型可以携带32个标识,并且可以随意组合。
如下操作,想完成什么操作传入对应的标记即可。
![](https://i-blog.csdnimg.cn/blog_migrate/89abfd5a4b3821ba66aeb3068af22d7c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/885044a158821e503f6aa57221a021ca.jpeg)
open()的常用标记
标记 | 含义 |
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
O_APPEND | 追加 |
O_CREAT | 创建文件 |
O_TRUNC | 清空文件 |
![](https://i-blog.csdnimg.cn/blog_migrate/ed54039b7ee95839ef4a8d42100c4c95.jpeg)
将只读标记和创建文件标记传入open()接口,创建文件如图。文件创建出来了,但可以看出它的权限是乱的,可见这个接口不足以像fopen() 一样打开文件。
![](https://i-blog.csdnimg.cn/blog_migrate/17c09554a4878c23f949096dd0d456cc.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/2f229da19b44ecebfd65d05c9b6e9e7b.png)
使用另一个接口创建出来权限正常的文件,但权限还不是如我们所设想的设置什么权限码就创建什么权限文件。此问题与权限掩码umask 有关
![](https://i-blog.csdnimg.cn/blog_migrate/57cfd7868b1e2504f5e1a26e60b05cd2.png)
权限码与umask取反再按位与最后得到的权限码才是最终权限码,若需只在该程序重设umask 只需使用接口umask:
mode_t umask(mode_t mask);
参数mask为想要重设的权限掩码
![](https://i-blog.csdnimg.cn/blog_migrate/e5626eb10561d4dbcb1a44041ba7a986.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/38cd386b8abe3d2bb0b95ff5fa26119f.png)
现在所创建的文件权限就全如程序员所愿了。
write()
系统写文件操作接口
ssize_t write(int fd, const void *buf, size_t count);
操作与fwrite() 基本一致,参数buf 类型为void* ,count 为要写入内容大小(byte)。
![](https://i-blog.csdnimg.cn/blog_migrate/a151c5d624f9e038d77e9b6af7ba69fb.jpeg)
写入成功,但如上打开方式还有一个隐患:
![](https://i-blog.csdnimg.cn/blog_migrate/08f2db42175f7c79ebd8d27af19a32af.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ff164f545eaf43abbfe552bf5b1c5f58.jpeg)
后写入的内容并不会在空文件写入,而是会在已有内容上进行覆盖,这样的操作可不像C语言的w操作,针对此问题可以再加一个标志O_TRUNC
![](https://i-blog.csdnimg.cn/blog_migrate/6e0ec1de188487302dd8e4e423c58f4c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8d9d78fa8cd5059e358e2468a68712dd.jpeg)
如此才可和C语言的fopen() 的 w 操作一致
read()
ssize_t read(int fd, void *buf, size_t count);
read()参数与write() 基本一致,与fread() 操作相差不多,需要buf 提前开好空间
![](https://i-blog.csdnimg.cn/blog_migrate/5ea1c4b9ea0a812099cdd0fd40148ada.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a29a025fad757586cfdbdd848b16e90f.jpeg)
当文件不存在时:
![](https://i-blog.csdnimg.cn/blog_migrate/cada985cefc4e856e57fe45aa2193ef8.png)
如此,效果与fread() 一致
close()
使用完文件后需得关闭文件,尽量防止内存泄漏,使用close()接口
int close(int fd);
两种操作接口联系
操作系统设计时为了安全任何高级语言操作文件都不能绕过操作系统,用户对文件操作只能通过操作系统提供的接口,而Linux操作系统内核主要用C语言编写,提供的接口也是用C语言编写的,C语言用户可以直接使用。C语言操作文件也需通过操作系统提供的接口,因此C语言的文件操作接口是对系统文件接口进行封装后暴露出来的。
但既然已经有系统接口了为什么还要C语言文件操作接口,此问题原因有几个:
使用不方便(对比之前的fopen() 和open() )
C语言是跨平台语言,若使用系统文件操作接口,同一份代码移植到其他平台将会编译不通过,使用封装后的接口,可以完美解决这个问题
高级语言都是用自己的语言特性封装系统接口来操作文件
系统接口 | 语言接口 |
open(const char* path, O_WRONLY | O_CREAT |
open(const char* path, O_RDONLY) | fopen(const char* path, "r") |
open(const char* path, W_WRONLY | O_CREAT |
close(int fd) | fclose(FILE* fStream) |
write(fd, const void* buf, size_t count) | fwrite(const void* ptr, size_t size, size_t number, FILE* fStream) |
read(fd, void* buf, size_t count) | fread(void* ptr, size_t size, size_t number, FILE* fStream) |
C语言对封装的接口跨平台解决办法
条件编译 + 穷举