5、文件的随机读写:fseek、ftell、rewind 函数的使用与文件定位

文件操作里,顺序读写是最基础的。但实际开发中,我们经常需要「跳着读」——比如日志文件太大,我只想看最后几行;或者数据库文件里,我想直接定位到第 N 条记录。这时候,随机读写就派上用场了。

说白了,随机读写就是让你在文件里「指哪打哪」。C 语言提供了三个核心函数:fseekftellrewind。我个人习惯把它们叫做「文件定位三剑客」。

5.1 文件位置指针:一个看不见的「光标」

每个打开的文件,系统都会维护一个内部指针,叫「文件位置指针」。它指向当前要读写的位置。你读一个字节,指针就往后挪一个字节。写也一样。

嗯,这里要注意:这个指针不是 C 语言里的指针变量,而是操作系统维护的一个偏移量。你没法直接拿它来加减,但可以通过函数来移动它。

核心概念:文件位置指针 = 当前读写位置(从文件头开始的字节偏移量)。

5.2 fseek:跳到指定位置

fseek 是随机读写的核心。它的原型长这样:

int fseek(FILE *stream, long offset, int whence);

三个参数:

  • stream:文件指针
  • offset:偏移量(正数往后,负数往前)
  • whence:起始位置(三个宏)

whence 有三个可选值:

含义
SEEK_SET 0 从文件头开始
SEEK_CUR 1 从当前位置开始
SEEK_END 2 从文件尾开始

举个例子:

// 跳到文件开头
fseek(fp, 0, SEEK_SET);

// 跳到文件末尾
fseek(fp, 0, SEEK_END);

// 从当前位置往后跳 100 个字节
fseek(fp, 100, SEEK_CUR);

// 从文件尾往前跳 50 个字节
fseek(fp, -50, SEEK_END);

我的经验:我在项目中遇到过一个问题——用 fseek 跳到文件尾后,直接 fread 读数据,结果读出来全是乱码。后来才发现,跳到文件尾后位置指针在末尾,再读当然没东西。正确的做法是先跳到目标位置,再读。你想想看,指针都到尽头了,还能读到啥?

5.3 ftell:告诉我你在哪

ftell 用来获取当前文件位置指针的偏移量。原型:

long ftell(FILE *stream);

返回从文件头到当前位置的字节数。如果出错,返回 -1L。

最常见的用法是获取文件大小:

// 获取文件大小
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
rewind(fp);  // 回到开头

注意:ftell 返回的是 long 类型,最大只能表示约 2GB 的文件。对于超大文件(比如视频文件),需要用 fgetposfsetpos,它们支持更大的文件。我曾经在一个日志分析工具里踩过这个坑——文件超过 2GB 后,ftell 返回负数,排查了半天才发现是溢出。

5.4 rewind:一键回到起点

rewind 相当于 fseek(fp, 0, SEEK_SET),但它还会清除文件结束标志和错误标志。原型:

void rewind(FILE *stream);

没有返回值。我个人习惯在读完文件后调用它,方便后续操作。

// 读取文件内容
char buffer[256];
fread(buffer, 1, 256, fp);

// 读完回到开头,准备重新读取
rewind(fp);

// 现在可以重新读一遍了
fread(buffer, 1, 256, fp);

5.5 实战:读取文件的最后 100 个字节

这个需求很常见——比如查看日志文件的尾部。用随机读写可以高效实现:

#include <stdio.h>

int main() {
    FILE *fp = fopen("log.txt", "rb");
    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }

    // 跳到文件末尾
    fseek(fp, 0, SEEK_END);
    long file_size = ftell(fp);

    // 如果文件小于 100 字节,就从开头读
    long read_size = (file_size > 100) ? 100 : file_size;

    // 跳到倒数第 100 字节的位置
    fseek(fp, -read_size, SEEK_END);

    // 读取并打印
    char buffer[101] = {0};
    fread(buffer, 1, read_size, fp);
    printf("文件最后 %ld 个字节:\n%s\n", read_size, buffer);

    fclose(fp);
    return 0;
}

避坑指南:我曾经在二进制文件上直接用 fseek 跳转到末尾附近,然后 fread 读字符串,结果发现读出来的内容有乱码。原因很简单——二进制文件里可能包含 '\0' 字符,而 printf 遇到 '\0' 就停了。正确的做法是逐字节读取并判断。

5.6 知识体系图

下面这张图帮你理清文件随机读写的核心逻辑:

文件随机读写核心逻辑 文件流(FILE *fp) 文件位置指针(当前偏移量) fseek 设置位置指针 ftell 获取位置指针 rewind 重置到开头 跳到指定位置读写 获取文件大小 读取文件尾部 三个函数配合使用,实现文件的精确定位与随机访问

5.7 常见错误与调试技巧

用随机读写时,有几个坑我经常遇到:

  • 忘记检查返回值:fseek 返回 0 表示成功,非 0 表示失败。我见过太多人直接忽略返回值,结果定位失败还不知道。
  • 文本模式下的定位问题:在 Windows 上,文本模式下换行符 \n 会被转换成 \r\n,导致 fseek 定位不准确。建议二进制文件用 "rb" 模式。
  • ftell 后忘记 rewind:获取文件大小后,位置指针在文件尾。如果不 rewind,后续读写会直接失败。

我曾经踩过的坑:有一次写一个配置文件解析器,用 fseek 跳到指定行。结果在 Linux 上跑得好好的,到 Windows 上就乱跳。查了半天才发现是文本模式下 \r\n 的问题。从那以后,凡是涉及随机读写的文件,我一律用二进制模式打开。

好了,文件随机读写就讲到这里。记住三个函数:fseek 负责跳,ftell 负责问,rewind 负责回。配合使用,文件操作就能随心所欲。


公众号:蓝海资料掘金营,微信 deep3321