一、文件读写核心原理
-
数据流模型:
-
文件被抽象为连续的字节流
-
通过文件指针(
FILE*
)管理当前位置 -
每次读写自动移动指针位置
-
-
缓冲机制:
// 默认缓冲区大小通常为 4096 字节(与内存页对齐) #define BUFSIZ 4096
-
写操作先存入缓冲区,满时触发真实磁盘写入
-
读操作预加载数据块到缓冲区
-
-
文本模式 vs 二进制模式:
二、主要读写函数对比
1. 字符级读写
int fputc(int c, FILE *stream); // 写单个字符
int fgetc(FILE *stream); // 读单个字符
// 示例:逐字符复制文件
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dest);
}
2. 行级读写
char *fgets(char *s, int size, FILE *stream); // 读取一行(包含换行符)
int fputs(const char *s, FILE *stream); // 写入字符串(不自动加换行符)
// 示例:读取配置文件
char line[256];
while(fgets(line, sizeof(line), fp)) {
printf("Line: %s", line);
}
3. 格式化读写
int fprintf(FILE *stream, const char *format, ...); // 格式化写入
int fscanf(FILE *stream, const char *format, ...); // 格式化读取
// 示例:写入结构体
struct Student s = {101, "Alice", 85.5};
fprintf(fp, "%d %s %.1f\n", s.id, s.name, s.score);
4. 块数据读写
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
// 示例:读写二进制数据
float data[100];
fwrite(data, sizeof(float), 100, fp); // 写入100个float
fread(data, sizeof(float), 100, fp); // 读取100个float
三、关键控制函数
1、文件定位:
int fseek(FILE *stream, long offset, int whence); // 移动指针
long ftell(FILE *stream); // 获取当前位置
void rewind(FILE *stream); // 重置到文件开头
whence
参数:
-
SEEK_SET
:文件开头 -
SEEK_CUR
:当前位置 -
SEEK_END
:文件末尾
2、缓冲区控制:
int fflush(FILE *stream); // 强制刷新缓冲区
setvbuf(fp, NULL, _IONBF, 0); // 关闭缓冲(实时写入)
四、完整工作流程示例
1. 二进制文件读写(结构体存储)
#include <stdio.h>
typedef struct {
int id;
char name[20];
double score;
} Student;
int main() {
FILE *fp = fopen("students.dat", "wb+");
if(!fp) { perror("Error"); return 1; }
// 写入数据
Student s1 = {1001, "Alice", 92.5};
Student s2 = {1002, "Bob", 88.0};
fwrite(&s1, sizeof(Student), 1, fp);
fwrite(&s2, sizeof(Student), 1, fp);
// 定位到开头并读取
rewind(fp);
Student temp;
while(fread(&temp, sizeof(Student), 1, fp) == 1) {
printf("ID: %d, Name: %s, Score: %.1f\n",
temp.id, temp.name, temp.score);
}
fclose(fp);
return 0;
}
2. 文本文件处理(CSV格式)
void write_csv(const char* filename) {
FILE *fp = fopen(filename, "w");
if(!fp) return;
fprintf(fp, "Name,Age,Salary\n");
fprintf(fp, "Alice,28,8500.50\n");
fprintf(fp, "Bob,35,12000.75\n");
fclose(fp);
}
void read_csv(const char* filename) {
FILE *fp = fopen(filename, "r");
if(!fp) return;
char header[100];
fgets(header, sizeof(header), fp); // 跳过标题行
char name[20];
int age;
float salary;
while(fscanf(fp, "%[^,],%d,%f\n", name, &age, &salary) == 3) {
printf("%s: %d years, $%.2f\n", name, age, salary);
}
fclose(fp);
}
五、关键错误处理
1、读写返回值校验:
// fread/fwrite 返回成功读写的元素数量
size_t count = fwrite(buffer, sizeof(int), 100, fp);
if(count != 100) {
// 处理写入不完整的情况
}
// 检查文件结束
if(feof(fp)) {
printf("Reached end of file\n");
}
// 检查错误标志
if(ferror(fp)) {
perror("File error");
clearerr(fp); // 清除错误标志
}
2、二进制文件校验:
// 写入文件头标识
const char MAGIC[4] = {'F', 'I', 'L', 'E'};
fwrite(MAGIC, sizeof(char), 4, fp);
// 读取时校验
char header[4];
if(fread(header, 1, 4, fp) != 4 ||
memcmp(header, MAGIC, 4) != 0) {
printf("Invalid file format\n");
}
六、性能优化技巧
1、批量读写:
// 低效方式:逐个写入
for(int i=0; i<1000000; i++) {
fwrite(&data[i], sizeof(int), 1, fp);
}
// 高效方式:批量写入
fwrite(data, sizeof(int), 1000000, fp);
2、内存映射文件(POSIX系统):
int fd = open("largefile.bin", O_RDWR);
void *map = mmap(NULL, file_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
// 直接操作内存即可修改文件
munmap(map, file_size);
close(fd);
3、设置合适缓冲区:
char buf[16384]; // 16KB 缓冲区
setvbuf(fp, buf, _IOFBF, sizeof(buf)); // 全缓冲模式
七、常见问题排查
1、文本文件数值错误:
-
现象:读取的浮点数精度异常
-
原因:使用
%f
读取时未考虑本地化设置 -
解决:
setlocale(LC_NUMERIC, "C"); // 强制使用点号小数点
2、结构体对齐问题:
-
现象:跨平台读取二进制结构体出错
-
解决:
#pragma pack(push, 1) // 1 字节对齐
typedef struct { ... }
#pragma pack(pop)
3、文件截断问题:
-
现象:新写入内容比原内容短时残留旧数据
-
解决:
freopen(NULL, "wb", fp); // 重新以写模式打开清空文件