4. 文件的随机读写

文件的随机读写是指能够在文件的任意位置直接读取或写入数据,而无需按顺序从头到尾遍历内容。

文件的随机读写常用于二进制方式读写文件的过程中。对于文本方式读写文件时,由于每一行字符串的长度难于确定。因此很少使用文件的随机读写。

随机读写通过移动文件的读写位置来快速定位到指定位置,实现高效的数据操作。在软件使用的过程中,在听 MP3 音乐时,我们向后拖动滚动条来跳过前面内容进行播放,实际就可能用到文件的随机读操作。

应用场景

  1. 需改部分内容。
  2. 高效查询‌。
  3. 二进制文件处理‌。
  4. 日志追加‌。

文件的随机读写相关的函数

函数
说明
long ftell(FILE *stream);
返回当前文件流 stream 的读写位置。
int fseek(FILE *stream, long offset, int whence);
设置当前文件流的读写位置,成功返回0,失败返回 -1。
void rewind(FILE *stream);
将文件的读写位置设置为 0。

说明:

  1. stream 参数的实际要操作的文件流对象。
  2. offset 参数是相对位置的偏移量。
    • 大于 0 的数代表向文件末尾方向移动。
    • 小于 0 的数代表向文件头方向移动。
    • 0 表示相对于某个位置不变。
  3. whence 参数是只相对于哪里进行移动。
    • SEEK_SET(值为 0),代表从文件头开始偏移。
    • SEEK_CUR(值为 1),代表从当前读写位置开始偏移。
    • SEEK_END(值为 2),代表从文件尾开始偏移。

示例:

上节课我们已经生成了以个长度为 80 字节的文件 mydata.txt 用来保存了两个学生的信息。我们已经知道内部的存储格式,接下来我们使用文件的随机读写获取文件中的部分信息。

// filename: file_random_read.c
#include <stdio.h>

struct student {
    char name[32];
    int age;
    int score;
};

int main(int argc, char * argv[]) {
    FILE * pf = NULL;  // 用于保存已经打开的文件
    struct student astu;  // 用于保存一个学生的信息
    int age;  // 用于保存一个学生的年龄。

    // 以二进制 读 的方式打开文件
    pf = fopen("mydata.txt", "rb");
    if (NULL == pf) {
        perror("打开文件 mydata.txt");
        return -1;
    }
    // 获取初始时文件的读写位置。
    printf("打开文件时,文件的读写位置是: %ld\n", ftell(pf));
    // 将读写位置定位到文件末尾,然后 ftell 的返回值就是文件的长度。
    fseek(pf, 0, SEEK_END);
    // 获取当前文件的读写位置。
    printf("文件的长度是: %ld\n", ftell(pf));

    // 从当前位置向前移动 40 个字节。
    fseek(pf, -40, SEEK_CUR);
    // 打印文件的当前读写位置。
    printf("第二个学生信息在文件的读写位置是: %ld\n", ftell(pf));
    // 读取第二个学生的信息
    if (fread(&astu, sizeof(astu), 1, pf) < 1) {
        printf("读取第二个学生信息失败\n");
        goto exit_main;  // 关闭文件后退出
    }
    printf("姓名: %s, 年龄: %d, 成绩: %d\n", astu.name, astu.age, astu.score);
    // 打印当前的读写位置信息
    printf("读取第二个学生信息后的文件读写位置是 :%ld\n", ftell(pf));
    // 重新定位文件头 第 32 个字节的位置,读取第一个学生的年龄信息
    fseek(pf, 32, SEEK_SET);
    if (fread(&age, sizeof(age), 1, pf) < 1) {
        printf("读取第一个学生的年龄失败!\n");
        goto exit_main;
    }
    printf("第一个学生的年龄是: %d\n", age);

exit_main:
    // 关闭文件
    fclose(pf);

    return 0;
}

编译和运行结果如下:

weimingze@mzstudio:~$ ls
file_random_read.c  mydata.txt
weimingze@mzstudio:~$ ls -l mydata.txt
-rw-r--r-- 1 weimingze weimingze 80 12月  6 10:16 mydata.txt
weimingze@mzstudio:~$ gcc -o file_random_read file_random_read.c
weimingze@mzstudio:~$ ./file_random_read
打开文件时,文件的读写位置是: 0
文件的长度是: 80
第二个学生信息在文件的读写位置是: 40
姓名: lisi, 年龄: 19, 成绩: 80
读取第二个学生信息后的文件读写位置是: 80
第一个学生的年龄是: 18

从上述运行结果可以看出,使用 fseek 可以任意定位一个文件的某个位置进行读写,只要这个文件的格式是已知的,这种定位读写的方法是可行的,并且效率高。

实验:

自己写程序,尝试使用 fseekftell 进行文件的随机定位的读写操作。