C语言读写文件的步骤一般是:
创建或打开文件 > 读或写文件 > 关闭文件
当然读或写的过程中还可以通过操作当前文件偏移量来控制读写位置。
下面分别介绍这些函数。
头文件:
#include <fcntl.h>
//此头文件定义了以下oflag
O_RDONLY //只读0
O_WRONLY //只写1
O_RDWR //读写2
//上面三个oflag必须选且只能选一个,下面是可选oflag
O_APPEND //追加
O_CREAT //若不存在则创建。使用此选项时需要第三个参数mode,用以指定新文件访问权限位。
O_EXCL //如果指定了O_CREAT,文件存在则报错,不存在则创建。测试文件存在和创建文件为原子操作。
O_TRUNC //若文件存在,且以只写或读写打开,则将其长度截短为0。
O_NOCTTY //若pathname为终端设备,则不将该设备分配作为此进程的控制终端。
O_NONBLOCK //若pathname为FIFO、块特殊文件或字符特殊文件,则为文件的本次打开操作和后续I/O操作设置非阻塞模式。
O_RSYNC //使每一个以文件描述符作为参数的read操作等待,直至任何对文件同一部分进行的未决写操作都完成。
O_DSYNC //使每次write等待物理I/O操作完成,但是如果写操作不影响读取刚写入的数据,则不等待文件属性被更新。
O_SYNC //使每次write等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。
O_SYNC和O_DSYNC区别:
O_SYNC:每次write后文件属性更新完成,write才返回,不管write是否写入新字节或文件内容是否被修改。
而O_DSYNC在write并不影响文件数据内容时,文件属性不会变。
打开:
#include <fcntl.h>
int open(const char *pathname, int oflag, ... /* mode_t mode */);
//成功返回文件描述符,出错返回-1
创建:
#include <fcntl.h>
int create(const char *pathname, mode_t mode);
//成功返回文件描述符,出错返回-1
//相当于:
open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);
//create后文件是只写方式打开,若要读,则需要先调用create、close,然后再open,也可以直接用下面的方式open:
open(pathname, O_RDWR | O_CREAT | O_TRUNC, mode);
关闭:
#include <unistd.h>
int close(int filedes);
//成功返回0,出错返回-1。当一个进程终止时,内核会自动关闭它所打开的所有文件。
移动文件偏移量:
lseek只将当前文件偏移量记录在内核中,不会引起任何I/O操作。
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);
//成功则返回新偏移量(从文件头开始计),出错返回-1。off_t类型是长整型。
whence参数决定了offset参数是从何处开始计算偏移量:
SEEK_SET //从文件头开始计,加上offset偏移量,offset为正
SEEK_CUR //从当前位置开始计,加上offset偏移量,offset为正或负
SEEK_END //从文件尾开始计,加上offset偏移量,offset为正正或负
若是将偏移量移动到大于当前文件的长度以后,再写入数据,文件中会形成一个空洞,文件大小会变为新的长度,但是虽然文件大小很大,其空洞部分却并不会占用额外的磁盘空间。
读取:
#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
//成功返回读取字节数,若已达到文件结尾返回0, 出错返回-1
void * 用于表示通用指针
ssize_t 为带符号的整数
size_t 不带符号整数
写:
#include <unistd.h>
ssize_t write(int filedes, const void *buf, size_t nbytes);
//成功返回已写字节数,出错返回-1
C语言中,任何需要多个函数调用的操作都不可能是原子操作,所以:
lseek(fd, 0L, SEEK_END);
write(fd, buf, 100);
上面这个追加文件内容的方式并非原子操作,多线程情况下可能会存在问题(线程A将线程B在文件尾部写入的内容覆盖掉)。
若要解决上面的问题,可以用open函数的O_APPEND标志,此标志会使内核每次对文件进行写之前,都会将进程的当前偏移量设置到文件尾端,然后再写,而且过程是原子的。
pread和pwrite:
#include <unistd.h>
ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
//返回值分别同read、write
pread相当于lseek和read, pwrite相当于lseek和write,但是有以下不同:
1、调用pread或pwrite时,无法中断其定位和读写操作(原子的)。
2、pread或pwrite操作不会更新文件偏移量。