文件 IO 编程
目录
基本文件操作
没有缓存的文件操作函数
open | 打开文件
函数原型
#include <sys/types.h> /* 提供类型 pid_t 的定义 */
#include <sys/stat.h>
#include <fcntl.h>
/**
* @brief 打开文件
*
* @param pathname 文件路径
* @param flags 打开方式
* - O_RDONLY 只读
* - O_WRONLY 只写
* - O_RDWR 读写
* - O_CREAT 文件不存在则创建
* - O_EXCL 与 O_CREAT 同时使用,文件存在则报错
* - O_NOCTTY ?
* - O_TRUNC 清空文件,然后写入
* - O_APPEND 追加写入
* @param mode 文件权限
* - S_IRUSR 用户读
* - S_IWUSR 用户写
* - S_IXUSR 用户执行
* - S_IRGRP 组读
* - S_IWGRP 组写
* - S_IXGRP 组执行
* - S_IROTH 其他读
* - S_IWOTH 其他写
* - S_IXOTH 其他执行
* @return int 文件描述符,失败返回 -1
*/
int open(const char *pathname, int flags, mode_t mode);
第三个参数 mode 只有在创建文件时才会生效
EG
// 打开文件,若文件不存在则创建,访问权限为 644
open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 打开文件,若文件不存在则创建,访问权限为 777
open("hello", O_RDWR | O_CREAT, 777);
close | 关闭文件
函数原型
#include <unistd.h>
/**
* @brief 关闭文件
*
* @param fd 文件描述符
* @return int 成功返回 0,失败返回 -1
*/
int close(int fd);
EG
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 关闭文件
close(fd);
read | 读取文件
函数原型
#include <unistd.h>
/**
* @brief 读取文件
*
* @param fd 文件描述符
* @param buf 读取缓冲区
* @param count 读取字节数
* @return ssize_t 成功返回读取的字节数,失败返回 -1,读到文件末尾返回 0
*/
ssize_t read(int fd, void *buf, size_t count);
EG
/* 读取文件,每次读取 1KB */
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 读取文件
char buf[1024];
read(fd, buf, sizeof(buf));
/* 读取文件,读完整个文件,打印出来 */
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 读取文件
char buf[1024];
int real_read_len;
while ((real_read_len = read(fd, buf, sizeof(buf))) > 0)
{
printf("%s", buf);
}
write | 写入文件
函数原型
#include <unistd.h>
/**
* @brief 写入文件
*
* @param fd 文件描述符
* @param buf 写入缓冲区
* @param count 写入字节数
* @return ssize_t 成功返回写入的字节数,失败返回 -1
*/
ssize_t write(int fd, const void *buf, size_t count);
EG
/* 写入文件,每次写入 1KB */
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 写入文件
char buf[1024];
write(fd, buf, sizeof(buf));
/* 写入文件,写入 10KB 的数据 */
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 写入文件
char buf[1024];
int i;
for (i = 0; i < 10; i++)
{
write(fd, buf, sizeof(buf));
}
lseek | 文件指针定位
#include <sys/types.h>
#include <unistd.h>
/**
* @brief 文件指针定位
*
* @param fd 文件描述符
* @param offset 偏移量
* @param whence 偏移起始位置
* - SEEK_SET 文件起始位置+偏移量
* - SEEK_CUR 当前位置+偏移量
* - SEEK_END 文件末尾+偏移量
* @return off_t 成功返回新的偏移量,失败返回 -1
*/
off_t lseek(int fd, off_t offset, int whence);
如果移动到文件初始位置之前,可能会返回 -1
EG
/* copy_file.c */
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#define BUFFER_SIZE 1024 /* 每次读写缓存大小,影响运行效率*/
#define SRC_FILE_NAME "src_file" /* 源文件名 */
#define DEST_FILE_NAME "dest_file" /* 目标文件名文件名 */
#define OFFSET 10240 /* 复制的数据大小 */
int main()
{
int src_file, dest_file;
unsigned char buff[BUFFER_SIZE];
int real_read_len;
/* 以只读方式打开源文件 */
src_file = open(SRC_FILE_NAME, O_RDONLY);
/* 以只写方式打开目标文件,若此文件不存在则创建该文件, 访问权限值为 644 */
dest_file = open(DEST_FILE_NAME, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (src_file < 0 || dest_file < 0)
{
printf("Open file error\n");
exit(1);
}
/* 将源文件的读写指针移到最后 10KB 的起始位置*/
lseek(src_file, -OFFSET, SEEK_END);
/* 读取源文件的最后 10KB 数据并写到目标文件中,每次读写 1KB */
while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0)
{
write(dest_file, buff, real_read_len);
}
close(dest_file);
close(src_file);
return 0;
}
// $ ./copy_file
// $ ls -lh dest_file
// -rw-r--r-- 1 david root 10K 14:06 dest_file
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd = open("./test.txt", O_RDWR|O_CREAT, 0666);
if (fd == -1)
{
// handle error
printf("open file error\n");
}
off_t offset = lseek(fd, 1024 * 10, SEEK_END); // move pointer 10KB before the end
if (offset == -1)
{
// handle error
printf("lseek error\n");
}
// 写入数据
char buf[1024] = "hello world";
int ret = write(fd, buf, sizeof(buf));
if (ret == -1)
{
// handle error
printf("write error\n");
}
close(fd);
return 0;
}
// 生成了一个11264字节(1024*10+1024)的文件,其中前10KB是空洞,后面的是hello world
文件锁
上锁来避免多个进程同时操作同一个文件
在 Linux 中,实现文件上锁的函数有 lockf()和 fcntl(),其中 lockf()用于对文件施加建议性锁,而 fcntl()不仅可以施加建议性锁,还可以施加强制锁。同时,fcntl()还能对文件的某一记录上锁,也就是记录锁。
fcntl | 文件锁
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
/**
* @brief 文件锁
*
* @param fd 文件描述符
* @param cmd 命令
* - F_DUPFD:复制文件描述符
* - F_GETFD:获得 fd 的 close-on-exec 标志,若标志未设置,则文件经过 exec()函数之后仍保持打开状态
* - F_SETFD:设置 close-on-exec 标志,该标志由参数 arg 的 FD_CLOEXEC 位决定
* - F_GETFL:得到 open 设置的标志
* - F_SETFL:改变 open 设置的标志
* - F_GETLK:根据 lock 参数值,决定是否上文件锁
* - F_SETLK:设置 lock 参数值的文件锁
* - F_SETLKW:这是 F_SETLK 的阻塞版本(命令名中的 W 表示等待(wait))。在无法获取锁时,会进入睡眠状态;如果可以获取锁或者捕捉到信号则会返回
* @param lock 锁,设置记录锁的具体信息
* @return int 成功返回 0,失败返回 -1
*/
int fcntl(int fd, int cmd, struct flock *lock);
struct flock
{
/**
* @brief 锁的类型
* @example F_RDLCK 读锁
* @example F_WRLCK 写锁
* @example F_UNLCK 解锁
*/
short l_type;
/**
* @brief 相对位移量
*/
off_t l_start;
/**
* @brief 锁的起始位置
* @example SEEK_SET 文件起始位置
* @example SEEK_CUR 文件当前位置
* @example SEEK_END 文件末尾位置
*/
short l_whence;
/**
* @brief 锁的长度
*/
off_t l_len;
/**
* 锁的属主进程
*/
pid_t l_pid;
};
EG
下面首先给出了使用 fcntl()函数的文件记录锁功能的代码实现。在该代码中,首先给 flock 结构体的对应位赋予相应的值。接着使用两次 fcntl()函数,分别用于判断文件是否可以上锁和给相关文件上锁,这里用到的 cmd 值分别为 F_GETLK 和 F_SETLK(或 F_SETLKW)。
用 F_GETLK 命令判断是否可以进行 flock 结构所描述的锁操作:若可以进行,则 flock 结构的 l_type 会被设置为 F_UNLCK,其他域不变;若不可行,则 l_pid 被设置为拥有文件锁的进程号,其他域不变。
用 F_SETLK 和 F_SETLKW 命令设置 flock 结构所描述的锁操作,后者是前者的阻塞版。
文件记录锁功能的源代码如下所示
/* lock_set.c */
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int lock_set(int fd, int type)
{
struct flock old_lock, lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = type;
lock.l_pid = -1;
/* 判断文件是否可以上锁 */
fcntl(fd, F_GETLK, &lock);
if (lock.l_type != F_UNLCK)
{
/* 判断文件不能上锁的原因 */
if (lock.l_type == F_RDLCK) /* 该文件已有读取锁 */
{
printf("Read lock already set by %d\n", lock.l_pid);
}
else if (lock.l_type == F_WRLCK) /* 该文件已有写入锁 */
{
printf("Write lock already set by %d\n", lock.l_pid);
}
}
/* l_type 可能已被 F_GETLK 修改过 */
lock.l_type = type;
/* 根据不同的 type 值进行阻塞式上锁或解锁 */
if ((fcntl(fd, F_SETLKW, &lock)) < 0)
{
printf("Lock failed:type = %d\n", lock.l_type);
return 1;
}
switch (lock.l_type)
{
case F_RDLCK:
{
printf("Read lock set by %d\n", getpid());
}
break;
case F_WRLCK:
{
printf("Write lock set by %d\n", getpid());
}
break;
case F_UNLCK:
{
printf("Release lock by %d\n", getpid());
return 1;
}
break;
default:
break;
} /* end of switch */
return 0;
}
下面的实例是文件写入锁的测试用例,这里首先创建了一个 hello 文件,之后对其上写入锁,最后释放写 入锁,代码如下所示:
/* write_lock.c */
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include "lock_set.c"
int main(void)
{
int fd;
/* 首先打开文件 */
fd = open("hello", O_RDWR | O_CREAT, 0644);
if (fd < 0)
{
printf("Open file error\n");
exit(1);
}
/* 给文件上写入锁 */
lock_set(fd, F_WRLCK);
getchar();
/* 给文件解锁 */
lock_set(fd, F_UNLCK);
getchar();
close(fd);
exit(0);
}
多路复用
标准 I/O 编程
上面的文件操作函数都是系统调用,不带缓存,当读写操作比较频繁时,会导致系统调用次数过多,降低效率,所以引入了标准 I/O 编程,带缓存的文件操作函数。
标准 I/O 提供了 3 种类型的缓冲存储:
- 全缓冲:在这种情况下,当填满标准 I/O 缓存后才进行实际 I/O 操作。存放在磁盘上的文件通常是由标准 I/O 库实施全缓冲的。在一个流上执行第一次 I/O 操作时,通常调用 malloc()就是使用全缓冲。
- 行缓冲:在这种情况下,当在输入和输出中遇到行结束符时,标准 I/O 库执行 I/O 操作。这允许我们一次输出一个字符(如 fputc()函数),但只有写了一行之后才进行实际 I/O 操作。标准输入和标准输出就是使用行缓冲的典型例子。
- 不带缓冲:标准 I/O 库不对字符进行缓冲。如果用标准 I/O 函数写若干字符到不带缓冲的流中,则相当于用系统调用 write()函数将这些字符全写到被打开的文件上。标准出错 stderr 通常是不带缓存的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个行结束符。
在下面讨论具体函数时,请读者注意区分以上的三种不同情况。
fopen() | 根据文件路径打开文件
#include <stdio.h>
/**
* @brief 根据文件路径打开文件
*
* @param pathname 文件路径
* @param type 打开方式
* - r 只读,文件必须存在
* - w 只写,文件不存在则创建,存在则清空文件
* - a 追加写入,文件不存在则创建
* - r+ 读写,文件必须存在
* - w+ 读写,文件不存在则创建,存在则清空文件
* - a+ 追加读写,文件不存在则创建
* 在以上方式后面加上 b,表示以二进制方式打开文件(Linux中不需要,系统会自己判断)
* @return FILE* 文件指针,失败返回 NULL
*/
FILE *fopen(const char *pathname, const char *type);
fdopen() | 根据文件描述符打开文件
#include <stdio.h>
/**
* @brief 根据文件描述符打开文件
*
* @param fd 文件描述符
* @param type 打开方式(同 fopen())
* @return FILE* 文件指针,失败返回 NULL
*/
FILE *fdopen(int fd, const char *type);
freopen() |
freopen()函数则是根据文件路径打开文件,并且可以指定文件指针。(重定向)
#include <stdio.h>
/**
* @brief 根据文件路径打开文件
*
* @param pathname 文件路径
* @param type 打开方式(同 fopen())
* @param stream 文件指针
* @return FILE* 文件指针,失败返回 NULL
*/
FILE *freopen(const char *pathname, const char *type, FILE *stream);
EG | 重定向输出到文件
/* freopen.c */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[80];
/* 打开文件 */
fp = freopen("test.txt", "w+", stdout);
if (fp == NULL)
{
printf("Open file error\n");
exit(1);
}
/* 向文件中写入数据 */
printf("This is a test for freopen()\n");
fclose(fp);
return 0;
}
fclose() | 关闭文件
#include <stdio.h>
/**
* @brief 关闭文件
*
* @param stream 文件指针
* @return int 成功返回 0,失败返回 -1
*/
int fclose(FILE *stream);
fread() | 读取文件
#include <stdio.h>
/**
* @brief 读取文件
*
* @param ptr 读取缓冲区
* @param size 每次读取的字节数
* @param nmemb 读取次数
* @param stream 文件指针
* @return size_t 成功返回读取的次数,失败返回 EOF,读到文件末尾返回 0
*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
EG
/* fread.c */
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[80];
/* 打开文件 */
fp = fopen("test.txt", "r");
if (fp == NULL)
{
printf("Open file error\n");
exit(1);
}
/* 读取文件 */
fread(buf, sizeof(buf), 1, fp);
printf("%s", buf);
fclose(fp);
return 0;
}
fwrite() | 写入文件
#include <stdio.h>
/**
* @brief 写入文件
*
* @param ptr 写入缓冲区
* @param size 每次写入的字节数
* @param nmemb 写入次数
* @param stream 文件指针
* @return size_t 成功返回写入的次数,失败返回 EOF
*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
EG
/* fwrite.c */
#include <stdio.h>
int main(void)
{
FILE *fp;
char buf[80] = "This is a test for fwrite()\n";
/* 打开文件 */
fp = fopen("test.txt", "w+");
if (fp == NULL)
{
printf("Open file error\n");
exit(1);
}
/* 写入文件 */
fwrite(buf, sizeof(buf), 1, fp);
fclose(fp);
return 0;
}
EG
#include <stdlib.h>
#include <stdio.h>
#define BUFFER_SIZE 1024 /* 每次读写缓存大小 */
#define SRC_FILE_NAME "src_file" /* 源文件名 */
#define DEST_FILE_NAME "dest_file" /* 目标文件名文件名 */
#define OFFSET 10240 /* 复制的数据大小 */
int main()
{
FILE *src_file, *dest_file;
unsigned char buff[BUFFER_SIZE];
int real_read_len;
/* 以只读方式打开源文件 */
src_file = fopen(SRC_FILE_NAME, "r");
/* 以写方式打开目标文件,若此文件不存在则创建 */
dest_file = fopen(DEST_FILE_NAME, "w");
if (!src_file || !dest_file)
{
printf("Open file error\n");
exit(1);
}
/* 将源文件的读写指针移到最后10KB 的起始位置*/
fseek(src_file, -OFFSET, SEEK_END);
/* 读取源文件的最后10KB 数据并写到目标文件中,每次读写1KB */
while ((real_read_len = fread(buff, 1, sizeof(buff), src_file)) > 0)
{
fwrite(buff, 1, real_read_len, dest_file);
}
fclose(dest_file);
fclose(src_file);
return 0;
}
其他操作
字符输入输出
#include <stdio.h>
/**
* @brief 从文件中读取一个字符
*
* @param stream 文件指针
* @return int 成功返回读取的字符,失败返回 EOF
*/
int getc(FILE *stream);
int fgetc(FILE *stream);
int getchar(void);
/**
* @brief 向文件中写入一个字符
*
* @param c 写入的字符
* @param stream 文件指针
* @return int 成功返回写入的字符,失败返回 EOF
*/
int putc(int c, FILE *stream);
int fputc(int c, FILE *stream);
int putchar(int c);
行输入输出
#include <stdio.h>
/**
* @brief 从文件中读取一行
*
* @param buf 读取缓冲区
* @param size 缓冲区大小
* @param stream 文件指针
* @return char* 成功返回读取的字符串,失败返回 NULL
*/
char *fgets(char *buf, int size, FILE *stream);
char *gets(char *buf);
/**
* @brief 向文件中写入一行
*
* @param buf 写入缓冲区
* @param stream 文件指针
* @return int 成功返回写入的字符,失败返回 EOF
*/
int fputs(const char *buf, FILE *stream);
int puts(const char *buf);
格式化输入输出
#include <stdio.h>
/**
* @brief 从文件中读取格式化数据
*
* @param stream 文件指针
* @param format 格式化字符串
* @param str 写入缓冲区
* @return int 成功返回读取的字符,失败返回 EOF
*/
int fscanf(FILE *stream, const char *format, ...);
int scanf(const char *format, ...);
int sscanf(const char *str, const char *format, ...);
/**
* @brief 向文件中写入格式化数据
*
* @param stream 文件指针
* @param format 格式化字符串
* @param ... 写入的数据
* @return int 成功返回写入的字符,失败返回 EOF
*/
int fprintf(FILE *stream, const char *format, ...);
int printf(const char *format, ...);
int sprintf(char *str, const char *format, ...);