在C语言中,文件随机读写(也称为直接访问或随机访问)允许程序在文件的任意位置进行读写操作,而不仅限于顺序读写。这在处理大型文件、数据库文件或需要频繁访问特定数据块的应用中尤为重要。本文将详细介绍C语言中实现文件随机读写的各种方法、相关函数、使用技巧以及注意事项,帮助你全面掌握这一重要技能。
![图片[1]_C语言文件随机读写的完全指南_知途无界](https://zhituwujie.com/wp-content/uploads/2025/10/d2b5ca33bd20251017094349.png)
一、文件随机读写的基本概念
1. 什么是文件随机读写?
文件随机读写,指的是程序能够在文件的任意位置进行读取或写入数据,而不需要按照数据在文件中存储的顺序依次进行。这与顺序读写相对,顺序读写需要从文件的起始位置开始,按照数据的排列顺序逐一读取或写入。
2. 为什么需要文件随机读写?
- 高效访问:对于大型文件,直接访问特定数据块比顺序查找更高效。
- 数据更新:在数据库或索引文件中,经常需要更新特定记录而不影响其他部分。
- 灵活性:允许程序根据需要动态地读取或写入文件的不同部分,适应复杂的数据处理需求。
二、文件随机读写的关键函数
在C语言中,文件随机读写主要依赖于标准库中的 <stdio.h> 提供的文件操作函数,特别是与文件指针定位相关的函数。以下是实现文件随机读写的核心函数:
1. fseek 函数
功能:设置文件指针的位置,实现文件的随机访问。
原型:
int fseek(FILE *stream, long int offset, int whence);
参数:
stream:指向FILE对象的指针,标识要操作的文件流。offset:偏移量,以字节为单位,表示从whence位置开始移动的字节数。whence:起始位置,指定偏移量的参考点,可以是以下值之一:SEEK_SET:文件开头(0)。SEEK_CUR:当前文件指针位置(1)。SEEK_END:文件末尾(2)。
返回值:
- 成功时返回
0。 - 失败时返回非零值(通常是
-1)。
示例:
FILE *file = fopen("example.txt", "rb+");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
// 将文件指针移动到文件的第100个字节处
if (fseek(file, 100, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
// 现在可以从第100个字节开始读取或写入
2. ftell 函数
功能:获取当前文件指针的位置,通常用于记录位置以便后续返回。
原型:
long int ftell(FILE *stream);
参数:
stream:指向FILE对象的指针,标识要操作的文件流。
返回值:
- 成功时返回当前文件指针的位置(从文件开头计算的字节数)。
- 失败时返回
-1L。
示例:
long int current_pos = ftell(file);
if (current_pos == -1L) {
perror("ftell失败");
fclose(file);
return 1;
}
printf("当前文件指针位置:%ld\n", current_pos);
3. rewind 函数
功能:将文件指针重新定位到文件的开头,同时清除文件结束标志和错误标志。
原型:
void rewind(FILE *stream);
参数:
stream:指向FILE对象的指针,标识要操作的文件流。
示例:
rewind(file); // 将文件指针移动到文件开头
4. fgetpos 和 fsetpos 函数(更可移植的方式)
功能:fgetpos 用于获取文件指针的位置,fsetpos 用于设置文件指针的位置。它们使用 fpos_t 类型,提供了比 ftell 和 fseek 更可移植的文件位置管理方式,尤其适用于处理大文件。
原型:
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
参数:
stream:指向FILE对象的指针,标识要操作的文件流。pos:指向fpos_t对象的指针,用于存储或设置文件位置。
返回值:
- 成功时返回
0。 - 失败时返回非零值。
示例:
fpos_t position;
// 获取当前文件位置
if (fgetpos(file, &position) != 0) {
perror("fgetpos失败");
fclose(file);
return 1;
}
// 移动文件指针到其他位置
if (fseek(file, 200, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
// 执行一些读写操作
// 恢复到之前的位置
if (fsetpos(file, &position) != 0) {
perror("fsetpos失败");
fclose(file);
return 1;
}
三、文件打开模式与随机读写
要实现文件的随机读写,文件的打开模式必须允许读写操作。常用的文件打开模式包括:
"r+":读写模式,文件必须存在,且从文件开头进行读写。"w+":读写模式,如果文件存在则截断为零长度,否则创建新文件。"a+":读写模式,文件指针位于文件末尾,允许读取和追加写入。"rb+"、"wb+"、"ab+":对应的二进制模式,用于处理二进制文件。
注意:对于文本文件和二进制文件,使用不同的模式(带 b 表示二进制模式),尤其是在不同操作系统间移植时,二进制模式可以避免文本模式下的换行符转换问题。
四、文件随机读写的具体实现
1. 写入数据到文件的指定位置
步骤:
- 使用
fopen以读写模式打开文件(如"rb+"或"wb+")。 - 使用
fseek将文件指针移动到目标位置。 - 使用
fwrite写入数据。 - 关闭文件。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("data.bin", "wb+");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
int data = 12345;
// 将数据写入文件的第10个字节位置
if (fseek(file, 10, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
if (fwrite(&data, sizeof(int), 1, file) != 1) {
perror("fwrite失败");
fclose(file);
return 1;
}
printf("数据已写入文件的第10个字节位置。\n");
fclose(file);
return 0;
}
2. 从文件的指定位置读取数据
步骤:
- 使用
fopen以读写模式打开文件(如"rb+"或"rb")。 - 使用
fseek将文件指针移动到目标位置。 - 使用
fread读取数据。 - 关闭文件。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("data.bin", "rb+");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
int data;
// 将文件指针移动到文件的第10个字节位置
if (fseek(file, 10, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
if (fread(&data, sizeof(int), 1, file) != 1) {
if (feof(file)) {
printf("到达文件末尾,未读取到数据。\n");
} else if (ferror(file)) {
perror("fread失败");
}
fclose(file);
return 1;
}
printf("从第10个字节位置读取到的数据:%d\n", data);
fclose(file);
return 0;
}
3. 更新文件中的特定数据
步骤:
- 打开文件以读写模式。
- 移动文件指针到需要更新的数据位置。
- 写入新的数据。
- 关闭文件。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("data.bin", "rb+");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
int new_data = 54321;
// 假设我们要更新第10个字节位置的数据
if (fseek(file, 10, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
if (fwrite(&new_data, sizeof(int), 1, file) != 1) {
perror("fwrite失败");
fclose(file);
return 1;
}
printf("第10个字节位置的数据已更新为:%d\n", new_data);
fclose(file);
return 0;
}
五、使用 fpos_t 进行更可移植的文件定位
对于需要更高可移植性,尤其是处理大文件时,推荐使用 fgetpos 和 fsetpos 函数,它们使用 fpos_t 类型来存储文件位置,能够处理超出 long 类型范围的文件位置。
示例:使用 fgetpos 和 fsetpos
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("data.bin", "wb+");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
int data1 = 11111;
int data2 = 22222;
// 写入一些初始数据
fwrite(&data1, sizeof(int), 1, file);
fwrite(&data2, sizeof(int), 1, file);
fpos_t pos;
// 获取当前文件位置(应该是8字节,假设int为4字节)
if (fgetpos(file, &pos) != 0) {
perror("fgetpos失败");
fclose(file);
return 1;
}
// 移动到第10个字节位置并写入新数据
if (fseek(file, 10, SEEK_SET) != 0) {
perror("fseek失败");
fclose(file);
return 1;
}
int data3 = 33333;
if (fwrite(&data3, sizeof(int), 1, file) != 1) {
perror("fwrite失败");
fclose(file);
return 1;
}
// 恢复到之前的位置
if (fsetpos(file, &pos) != 0) {
perror("fsetpos失败");
fclose(file);
return 1;
}
int read_data1, read_data2;
if (fread(&read_data1, sizeof(int), 1, file) != 1) {
perror("fread失败");
fclose(file);
return 1;
}
if (fread(&read_data2, sizeof(int), 1, file) != 1) {
perror("fread失败");
fclose(file);
return 1;
}
printf("读取到的数据1:%d\n", read_data1);
printf("读取到的数据2:%d\n", read_data2);
fclose(file);
return 0;
}
六、文件随机读写的注意事项
- 文件打开模式:
- 确保以正确的模式打开文件,允许读写操作(如
"rb+"、"wb+"、"ab+")。 - 文本模式(不带
b)在某些操作系统上可能会对换行符进行转换,影响文件指针的准确定位,尤其是在跨平台应用中。因此,推荐在需要精确定位的场景中使用二进制模式(带b)。
- 确保以正确的模式打开文件,允许读写操作(如
- 文件指针位置:
- 文件指针的位置是基于字节的,因此在读写不同大小的数据类型时,需要准确计算偏移量。
- 使用
sizeof运算符来确定数据类型的大小,确保偏移量和读写操作的正确性。
- 错误处理:
- 始终检查文件操作函数的返回值,如
fseek、fread、fwrite等,以确保操作成功。 - 使用
perror或strerror(errno)来输出错误信息,便于调试。
- 始终检查文件操作函数的返回值,如
- 缓冲区管理:
- 标准库函数通常使用缓冲区来提高性能,但在某些需要即时写入或读取的场景中,可能需要使用
fflush函数刷新缓冲区,或以无缓冲模式打开文件。
- 标准库函数通常使用缓冲区来提高性能,但在某些需要即时写入或读取的场景中,可能需要使用
- 并发访问:
- 在多线程或多进程环境中,同时访问同一个文件可能导致数据竞争和不一致。需要使用适当的同步机制(如文件锁)来管理并发访问。
- 大文件支持:
- 对于超过
long类型范围的大文件,使用fgetpos和fsetpos更加可移植和安全。 - 确保编译器和平台支持大文件操作,必要时定义适当的宏(如
_FILE_OFFSET_BITS=64在某些系统上)。
- 对于超过
七、综合示例:随机读写学生信息
以下是一个综合示例,展示如何使用文件随机读写来管理学生信息(如学号和姓名)。每个学生的信息占用固定大小的记录,便于随机访问。
示例说明:
- 每个学生记录包含一个整型学号(
int,4字节)和一个字符串姓名(假设最大长度为50字节,包括终止符)。 - 每个记录总大小为
4 + 50 = 54字节。 - 程序允许用户添加学生信息、根据学号查找学生信息、以及显示所有学生信息。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define RECORD_SIZE 54
#define MAX_NAME_LEN 50
#define FILENAME "students.dat"
typedef struct {
int id;
char name[MAX_NAME_LEN];
} Student;
void add_student(FILE *file) {
Student s;
printf("请输入学号: ");
scanf("%d", &s.id);
printf("请输入姓名: ");
scanf("%s", s.name);
// 移动到文件末尾
fseek(file, 0, SEEK_END);
if (fwrite(&s, RECORD_SIZE, 1, file) != 1) {
perror("写入学生信息失败");
} else {
printf("学生信息已添加。\n");
}
}
void find_student(FILE *file, int id) {
Student s;
rewind(file); // 移动到文件开头
while (fread(&s, RECORD_SIZE, 1, file) == 1) {
if (s.id == id) {
printf("找到学生: 学号=%d, 姓名=%s\n", s.id, s.name);
return;
}
}
printf("未找到学号为 %d 的学生。\n", id);
}
void display_all_students(FILE *file) {
Student s;
rewind(file);
printf("所有学生信息:\n");
while (fread(&s, RECORD_SIZE, 1, file) == 1) {
printf("学号=%d, 姓名=%s\n", s.id, s.name);
}
}
int main() {
FILE *file = fopen(FILENAME, "rb+");
if (file == NULL) {
// 如果文件不存在,尝试创建
file = fopen(FILENAME, "wb+");
if (file == NULL) {
perror("无法创建或打开文件");
return 1;
}
}
int choice, id;
while (1) {
printf("\n学生信息管理系统\n");
printf("1. 添加学生\n");
printf("2. 查找学生\n");
printf("3. 显示所有学生\n");
printf("4. 退出\n");
printf("请选择操作: ");
scanf("%d", &choice);
switch (choice) {
case 1:
add_student(file);
break;
case 2:
printf("请输入要查找的学号: ");
scanf("%d", &id);
find_student(file, id);
break;
case 3:
display_all_students(file);
break;
case 4:
fclose(file);
printf("退出程序。\n");
return 0;
default:
printf("无效的选择,请重新输入。\n");
}
}
fclose(file);
return 0;
}
代码说明:
- 数据结构:定义了一个
Student结构体,包含学号(int)和姓名(char数组)。 - 记录大小:每个学生记录固定为
RECORD_SIZE字节,便于随机访问。 - 文件打开:程序尝试以
"rb+"模式打开文件,如果文件不存在,则以"wb+"模式创建文件。 - 功能:
- 添加学生:将新学生信息写入文件末尾。
- 查找学生:从文件开头顺序查找指定学号的学生信息(可以根据需求优化为随机访问)。
- 显示所有学生:从文件开头顺序读取并显示所有学生信息。
- 退出:关闭文件并退出程序。
注意:上述示例中的查找功能是顺序查找,适用于小规模数据。对于大规模数据,可以通过记录的固定大小和学号的索引,实现更高效的随机查找。
八、高级主题:使用内存映射文件(Memory-Mapped Files)
除了标准的文件操作函数,C语言还可以通过内存映射文件技术实现高效的文件随机读写,尤其是在处理非常大的文件时。内存映射文件将文件的内容映射到进程的地址空间,使得文件的读写操作可以像访问内存一样高效。
1. 内存映射文件的概念
内存映射文件(Memory-Mapped Files)是一种将文件或文件的一部分映射到进程的地址空间的技术。通过这种方式,程序可以像访问内存一样访问文件数据,操作系统负责将内存中的更改同步回文件。
2. 在C语言中实现内存映射文件
在标准C语言中,并没有直接提供内存映射文件的函数。但是,可以通过平台相关的API实现,例如:
- POSIX系统(如Linux、macOS):使用
mmap函数。 - Windows系统:使用
CreateFileMapping和MapViewOfFile函数。
由于内存映射文件涉及平台相关的代码,这里不做详细展开。如果需要处理超大文件或追求极高的性能,建议学习并使用这些高级技术。
九、总结
文件随机读写是C语言文件操作中的重要部分,通过灵活使用 fseek、ftell、fgetpos、fsetpos 等函数,可以在文件的任意位置进行高效的读写操作。这在处理大型文件、数据库文件或需要频繁访问特定数据块的应用中尤为有用。
关键点回顾:
- 核心函数:
fseek:设置文件指针位置。ftell:获取当前文件指针位置。fgetpos和fsetpos:更可移植的文件位置管理。rewind:将文件指针重置到文件开头。
- 文件打开模式:确保以允许读写的模式(如
"rb+"、"wb+")打开文件,以支持随机访问。 - 数据对齐与大小:使用
sizeof运算符确保读写的数据类型大小与文件中的记录大小匹配,保证文件指针的准确定位。 - 错误处理:始终检查文件操作函数的返回值,确保操作成功,并处理可能的错误情况。
- 可移植性与大文件支持:对于需要处理大文件或跨平台应用,优先使用
fgetpos和fsetpos,并考虑大文件支持的相关设置。 - 综合应用:通过结构体和固定大小的记录,可以实现高效的数据管理和随机访问,如示例中的学生信息管理系统。
通过掌握文件随机读写的技巧,你可以编写出更高效、灵活的C语言程序,满足各种复杂的数据处理需求。在实际开发中,根据具体应用场景选择最合适的文件操作方式,是实现高效、可靠程序的关键。

























暂无评论内容