5、文件的随机读写:fseek、ftell、rewind 函数的使用与文件定位
文件操作里,顺序读写是最基础的。但实际开发中,我们经常需要「跳着读」——比如日志文件太大,我只想看最后几行;或者数据库文件里,我想直接定位到第 N 条记录。这时候,随机读写就派上用场了。
说白了,随机读写就是让你在文件里「指哪打哪」。C 语言提供了三个核心函数:fseek、ftell 和 rewind。我个人习惯把它们叫做「文件定位三剑客」。
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 的文件。对于超大文件(比如视频文件),需要用 fgetpos 和 fsetpos,它们支持更大的文件。我曾经在一个日志分析工具里踩过这个坑——文件超过 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 知识体系图
下面这张图帮你理清文件随机读写的核心逻辑:
5.7 常见错误与调试技巧
用随机读写时,有几个坑我经常遇到:
- 忘记检查返回值:fseek 返回 0 表示成功,非 0 表示失败。我见过太多人直接忽略返回值,结果定位失败还不知道。
- 文本模式下的定位问题:在 Windows 上,文本模式下换行符 \n 会被转换成 \r\n,导致 fseek 定位不准确。建议二进制文件用 "rb" 模式。
- ftell 后忘记 rewind:获取文件大小后,位置指针在文件尾。如果不 rewind,后续读写会直接失败。
我曾经踩过的坑:有一次写一个配置文件解析器,用 fseek 跳到指定行。结果在 Linux 上跑得好好的,到 Windows 上就乱跳。查了半天才发现是文本模式下 \r\n 的问题。从那以后,凡是涉及随机读写的文件,我一律用二进制模式打开。
好了,文件随机读写就讲到这里。记住三个函数:fseek 负责跳,ftell 负责问,rewind 负责回。配合使用,文件操作就能随心所欲。
公众号:蓝海资料掘金营,微信 deep3321