Skip to content

文件 IO 编程

目录

基本文件操作

没有缓存的文件操作函数

open | 打开文件

函数原型

c
#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

c
// 打开文件,若文件不存在则创建,访问权限为 644
open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

// 打开文件,若文件不存在则创建,访问权限为 777
open("hello", O_RDWR | O_CREAT, 777);

close | 关闭文件

函数原型

c
#include <unistd.h>

/**
  * @brief 关闭文件
  *
  * @param fd 文件描述符
  * @return int 成功返回 0,失败返回 -1
  */
int close(int fd);

EG

c
// 打开文件
int fd = open("hello", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// 关闭文件
close(fd);

read | 读取文件

函数原型

c
#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

c
/* 读取文件,每次读取 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 | 写入文件

函数原型

c
#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

c
/* 写入文件,每次写入 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 | 文件指针定位

c
#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

c
/* 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
c
#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 | 文件锁

c
#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 结构所描述的锁操作,后者是前者的阻塞版。

文件记录锁功能的源代码如下所示

c
/* 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 文件,之后对其上写入锁,最后释放写 入锁,代码如下所示:

c
/* 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);
}

多路复用

彻底理解 IO 多路复用实现机制 | 掘金

标准 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() | 根据文件路径打开文件

c
#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() | 根据文件描述符打开文件

c
#include <stdio.h>

/**
  * @brief 根据文件描述符打开文件
  *
  * @param fd 文件描述符
  * @param type 打开方式(同 fopen())
  * @return FILE* 文件指针,失败返回 NULL
  */
FILE *fdopen(int fd, const char *type);

freopen() |

freopen()函数则是根据文件路径打开文件,并且可以指定文件指针。(重定向)

c
#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 | 重定向输出到文件

c
/* 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() | 关闭文件

c
#include <stdio.h>

/**
  * @brief 关闭文件
  *
  * @param stream 文件指针
  * @return int 成功返回 0,失败返回 -1
  */
int fclose(FILE *stream);

fread() | 读取文件

c
#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

c
/* 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() | 写入文件

c
#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

c
/* 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

c
#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;
}

其他操作

字符输入输出

c
#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);

行输入输出

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);

格式化输入输出

c
#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, ...);

Copyright © 2022 田园幻想乡 浙ICP备2021038778号-1