提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、文件是什么?
- 二、文件的打开和关闭(C语言)
- 三、文件的顺序读写
-
- [1. 基本概念](#1. 基本概念)
- [2. 顺序读写的函数](#2. 顺序读写的函数)
-
- [2.1 字符读写](#2.1 字符读写)
- [2.2 字符串读写](#2.2 字符串读写)
- [2.3 格式化读写](#2.3 格式化读写)
- [3. 应用示例](#3. 应用示例)
-
- [3.1 文件复制](#3.1 文件复制)
- [3.2 日志记录](#3.2 日志记录)
- [4. 注意事项](#4. 注意事项)
- [5. 性能考虑](#5. 性能考虑)
- 四、文件的随机读写
-
- [1. 文件指针的概念](#1. 文件指针的概念)
- [2. 文件指针定位函数](#2. 文件指针定位函数)
- [3. 随机读写示例](#3. 随机读写示例)
- [4. 注意事项](#4. 注意事项)
- 五、文件缓冲区(C语言)
-
- [1. 缓冲区的概念与作用](#1. 缓冲区的概念与作用)
- [2. 缓冲区的类型](#2. 缓冲区的类型)
- [3. 缓冲区相关函数](#3. 缓冲区相关函数)
- [4. 缓冲区使用注意事项](#4. 缓冲区使用注意事项)
- [5. 缓冲区示例代码](#5. 缓冲区示例代码)
- 六、更新文件(C语言)
-
- [1. 文件更新概述](#1. 文件更新概述)
- [2. 基本操作函数](#2. 基本操作函数)
-
- [2.1 fopen()函数](#2.1 fopen()函数)
- [2.2 fseek()函数](#2.2 fseek()函数)
- [3. 常见更新操作示例](#3. 常见更新操作示例)
-
- [3.1 追加内容](#3.1 追加内容)
- [3.2 修改指定位置内容](#3.2 修改指定位置内容)
- [3.3 插入内容](#3.3 插入内容)
- [4. 注意事项](#4. 注意事项)
- [5. 高级技巧](#5. 高级技巧)
-
- [5.1 内存映射文件](#5.1 内存映射文件)
- [5.2 事务处理](#5.2 事务处理)
- [6. 实际应用场景](#6. 实际应用场景)
- 总结
前言
本文主要讲解文件操作相关的知识。
一、文件是什么?
1.文件的作用与分类
持久化存储数据
程序运行时产生的数据存储在内存中,程序退出后内存被回收,数据丢失。文件将数据保存到磁盘(硬盘),实现数据的持久化存储,确保程序多次运行时可访问历史数据。
文件的分类
从功能角度分为程序文件和数据文件:
- 程序文件 :包括源代码(
.c)、目标文件(.obj)、可执行程序(.exe)。 - 数据文件:存储程序运行时读写的数据,如输入数据或输出结果。本章重点讨论数据文件。
2.文件名结构
文件标识由三部分组成,例如 c:\code\test.txt:
- 文件路径 :
c:\code\ - 文件名主干 :
test - 文件后缀 :
.txt
3.二进制文件与文本文件
根据数据组织形式分为两类:
- 文本文件 :以ASCII码存储字符数据。例如整数
10000以ASCII形式存储占5字节(每个字符1字节)。 - 二进制文件 :直接以内存中的二进制形式存储数据。同一整数
10000仅占4字节(按整型存储)。
存储差异示例
- 文本文件:字符按ASCII存储,数值可转为ASCII或二进制。
- 二进制文件:数据无转换,存储紧凑,效率更高。
4.数据文件的应用场景
- 终端交互:默认从键盘输入、输出到显示器。
- 磁盘文件:需长期保存或大量数据时,读写磁盘文件更高效。
二、文件的打开和关闭(C语言)
在C语言中,文件操作是程序与外部存储设备交互的重要方式。要操作文件,首先需要打开文件,操作完成后需要关闭文件。以下是关于文件打开和关闭的详细说明:
1. 文件打开(fopen函数)
基本语法
c
FILE *fopen(const char *filename, const char *mode);
参数说明
filename:要打开的文件名(包含路径)mode:打开模式,指定文件的操作方式
常用打开模式
| 模式 | 描述 | 文件存在 | 文件不存在 |
|---|---|---|---|
| "r" | 只读 | 打开成功 | 返回NULL |
| "w" | 只写 | 清空内容 | 创建新文件 |
| "a" | 追加 | 追加内容 | 创建新文件 |
| "r+" | 读写 | 打开成功 | 返回NULL |
| "w+" | 读写 | 清空内容 | 创建新文件 |
| "a+" | 读写 | 追加内容 | 创建新文件 |
二进制文件模式
在以上模式后加"b"表示二进制模式,如"rb"、"wb+"等
返回值
- 成功:返回FILE指针(文件指针)
- 失败:返回NULL
示例代码
c
FILE *fp;
fp = fopen("example.txt", "r");
if(fp == NULL) {
printf("文件打开失败\n");
exit(1); // 退出程序
}
2. 文件关闭(fclose函数)
基本语法
c
int fclose(FILE *stream);
参数说明
stream:要关闭的文件指针
返回值
- 成功:返回0
- 失败:返回EOF(-1)
示例代码
c
if(fclose(fp) != 0) {
printf("文件关闭失败\n");
}
3. 注意事项
- 错误处理:每次打开文件后都应检查返回值是否为NULL
- 资源释放:打开的文件必须关闭,否则可能导致资源泄露
- 缓冲区刷新:fclose会刷新缓冲区,确保数据写入文件
- 路径表示:Windows中使用"\"或"/"表示路径,如"C:\data\file.txt"
4. 完整示例
c
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
// 打开文件
fp = fopen("data.txt", "w");
if(fp == NULL) {
printf("无法创建文件\n");
return 1;
}
// 文件操作...
fprintf(fp, "这是写入文件的内容\n");
// 关闭文件
if(fclose(fp) != 0) {
printf("文件关闭出错\n");
return 1;
}
return 0;
}
5. 常见问题
- 权限问题:如果没有写权限,以写模式打开文件会失败
- 文件锁定:某些系统会锁定正在使用的文件
- 路径问题:相对路径是相对于程序运行时的当前目录
- 缓冲区问题:未关闭文件可能导致数据未完全写入
三、文件的顺序读写
1. 基本概念
顺序读写是指按照文件中数据的物理存储顺序依次进行读写操作。与随机读写不同,顺序读写每次操作都会从当前位置开始,完成后文件指针会自动移动到下一个位置。
2. 顺序读写的函数
C语言提供了多个用于顺序读写文件的函数:
2.1 字符读写
-
fgetc(): 从文件中读取一个字符cint ch = fgetc(fp); // fp为文件指针 -
fputc(): 向文件写入一个字符cfputc('A', fp); // 向文件写入字符'A'
2.2 字符串读写
-
fgets(): 从文件中读取一行字符串cchar str[100]; fgets(str, 100, fp); // 最多读取99个字符 -
fputs(): 向文件写入字符串cfputs("Hello World", fp);
2.3 格式化读写
-
fscanf(): 从文件格式化读取cint age; fscanf(fp, "%d", &age); // 从文件读取整数 -
fprintf(): 向文件格式化写入cfprintf(fp, "Name: %s, Age: %d", "John", 25);
3. 应用示例
3.1 文件复制
c
#include <stdio.h>
int main() {
FILE *src = fopen("source.txt", "r");
FILE *dest = fopen("dest.txt", "w");
if(src == NULL || dest == NULL) {
printf("文件打开失败");
return 1;
}
int ch;
while((ch = fgetc(src)) != EOF) {
fputc(ch, dest);
}
fclose(src);
fclose(dest);
return 0;
}
3.2 日志记录
c
#include <stdio.h>
#include <time.h>
void log_message(const char *msg) {
FILE *log = fopen("app.log", "a");
if(log == NULL) return;
time_t now;
time(&now);
fprintf(log, "[%s] %s\n", ctime(&now), msg);
fclose(log);
}
4. 注意事项
- 每次读写操作后,文件指针会自动后移
- 到达文件末尾时,
fgetc()会返回EOF - 打开文件后必须检查文件指针是否为NULL
- 操作完成后必须调用
fclose()关闭文件 - 对于文本文件,不同操作系统可能有不同的换行符表示方式
5. 性能考虑
- 对于大文件,逐个字符读写效率较低
- 可以考虑使用缓冲区或块读写提高性能
- 频繁打开关闭文件会影响性能,应尽量减少操作次数
四、文件的随机读写
1. 文件指针的概念
文件指针是一个指示当前读写位置的标记,它记录了在文件中进行操作的位置。在随机读写文件时,我们需要通过移动文件指针来定位到特定的位置进行操作。
文件指针具有以下特性:
- 打开文件时,指针默认指向文件开头(位置0)
- 每次读写操作后,指针会自动移动到下一个位置
- 可以通过特定函数手动移动指针位置
2. 文件指针定位函数
fseek函数
c
int fseek(FILE *stream, long offset, int origin);
- 参数说明:
- stream:文件指针
- offset:偏移量(字节数),可正可负
- origin:基准位置,可取以下值:
- SEEK_SET:文件开头
- SEEK_CUR:当前位置
- SEEK_END:文件末尾
示例:
c
fseek(fp, 10, SEEK_SET); // 将指针移动到距文件开头10字节处
fseek(fp, -5, SEEK_END); // 将指针移动到距文件末尾前5字节处
ftell函数
c
long ftell(FILE *stream);
返回当前文件指针的位置(相对于文件开头的字节偏移量)
示例:
c
long pos = ftell(fp); // 获取当前指针位置
rewind函数
c
void rewind(FILE *stream);
将文件指针重置到文件开头,相当于:
c
fseek(fp, 0, SEEK_SET);
3. 随机读写示例
示例1:读取文件中间部分内容
c
FILE *fp = fopen("data.txt", "r");
if(fp == NULL) {
perror("文件打开失败");
return;
}
// 定位到文件中间位置
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, size/2, SEEK_SET);
// 读取中间部分内容
char buffer[100];
fread(buffer, 1, 50, fp);
fclose(fp);
示例2:修改二进制文件中的特定记录
c
typedef struct {
int id;
char name[20];
float score;
} Student;
// 修改第3条记录的成绩
FILE *fp = fopen("students.dat", "r+");
if(fp == NULL) {
perror("文件打开失败");
return;
}
// 定位到第3条记录
fseek(fp, 2 * sizeof(Student), SEEK_SET);
Student stu;
fread(&stu, sizeof(Student), 1, fp);
// 修改成绩
stu.score = 95.5;
// 写回文件
fseek(fp, 2 * sizeof(Student), SEEK_SET);
fwrite(&stu, sizeof(Student), 1, fp);
fclose(fp);
4. 注意事项
-
二进制文件与文本文件:
- 文本文件的随机读写可能会因系统不同而产生差异
- 二进制文件的随机读写更加可靠
-
边界检查:
- 移动指针时要注意不要超出文件范围
- 可以使用ftell和fseek结合检查文件大小
-
错误处理:
- 检查fseek的返回值(成功返回0,失败返回非0)
- 处理可能的文件定位错误
-
性能考虑:
- 频繁的随机访问可能会降低IO性能
- 对于大量随机访问,考虑使用内存缓存
五、文件缓冲区(C语言)
1. 缓冲区的概念与作用
文件缓冲区是内存中的一块区域,用于临时存储文件读写的数据。缓冲区的主要作用包括:
- 提高I/O效率:减少直接访问磁盘的次数,通过批量读写提高性能
- 协调速度差异:解决CPU处理速度与磁盘I/O速度不匹配的问题
- 数据暂存:为流式I/O操作提供数据中转站
示例场景:当程序调用fwrite()写入100字节数据时,数据不会立即写入磁盘,而是先存入缓冲区,待缓冲区满或显式刷新时才执行实际磁盘写入。
2. 缓冲区的类型
C语言中主要有三种缓冲模式:
-
全缓冲(Fully Buffered)
- 缓冲区满时才执行实际I/O操作
- 典型应用:普通文件读写
- 缓冲区大小通常为BUFSIZ(在stdio.h中定义,通常为512或4096字节)
-
行缓冲(Line Buffered)
- 遇到换行符'\n'或缓冲区满时刷新
- 典型应用:终端I/O(stdin/stdout)
- 示例:printf()输出通常会在遇到\n或程序正常结束时自动刷新
-
无缓冲(Unbuffered)
- 立即执行I/O操作
- 典型应用:stderr错误输出
- 示例:perror()会立即将错误信息输出到屏幕
3. 缓冲区相关函数
-
设置缓冲模式
cint setvbuf(FILE *stream, char *buf, int mode, size_t size);- 参数说明:
- stream:文件指针
- buf:用户提供的缓冲区(为NULL时由库自动分配)
- mode:_IOFBF(全缓冲)/_IOLBF(行缓冲)/_IONBF(无缓冲)
- size:缓冲区大小
- 参数说明:
-
强制刷新缓冲区
cint fflush(FILE *stream);- 将缓冲区内容立即写入文件
- stream为NULL时刷新所有输出流
-
关闭文件时的自动刷新
cint fclose(FILE *stream);- 关闭文件前会自动调用fflush()
- 示例:程序异常终止时可能丢失未刷新的缓冲区数据
4. 缓冲区使用注意事项
-
数据一致性问题
- 重要数据应及时fflush(),防止程序崩溃导致数据丢失
- 示例:数据库日志记录应先fflush()再执行关键操作
-
性能调优
- 大文件处理时可适当增大缓冲区提高性能
- 示例:视频处理程序可设置8KB或更大的缓冲区
-
跨平台差异
- 不同系统下默认缓冲区大小可能不同
- 示例:Windows和Linux的BUFSIZ可能不同
-
线程安全
- 多线程环境下操作同一文件需加锁保护
- 示例:使用flockfile()/funlockfile()函数族
5. 缓冲区示例代码
c
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp = fopen("test.dat", "w");
if (!fp) {
perror("fopen failed");
return EXIT_FAILURE;
}
// 设置8KB的全缓冲区
char buf[8192];
if (setvbuf(fp, buf, _IOFBF, sizeof(buf)) != 0) {
perror("setvbuf failed");
fclose(fp);
return EXIT_FAILURE;
}
// 写入数据(先存入缓冲区)
for (int i = 0; i < 1000; i++) {
fprintf(fp, "Record %d\n", i);
}
// 强制刷新缓冲区
fflush(fp);
// 修改为行缓冲模式
setvbuf(fp, NULL, _IOLBF, 0);
fprintf(fp, "This will be line buffered\n");
fclose(fp);
return EXIT_SUCCESS;
}
六、更新文件(C语言)
1. 文件更新概述
在C语言中,文件更新操作主要包括以下几种方式:
- 追加内容到文件末尾
- 修改文件中特定位置的内容
- 替换文件中的部分内容
- 插入新内容到文件指定位置
2. 基本操作函数
2.1 fopen()函数
c
FILE *fopen(const char *filename, const char *mode);
常用打开模式:
- "r":只读
- "w":只写(会清空原文件)
- "a":追加(在文件末尾写入)
- "r+":读写(从文件头开始)
- "w+":读写(清空原文件)
- "a+":读写(从文件末尾开始)
2.2 fseek()函数
c
int fseek(FILE *stream, long offset, int whence);
参数说明:
whence取值:- SEEK_SET:从文件头开始
- SEEK_CUR:从当前位置开始
- SEEK_END:从文件末尾开始
3. 常见更新操作示例
3.1 追加内容
c
FILE *fp = fopen("example.txt", "a");
if(fp != NULL) {
fprintf(fp, "This will be appended to the end.\n");
fclose(fp);
}
3.2 修改指定位置内容
c
FILE *fp = fopen("example.dat", "r+");
if(fp != NULL) {
fseek(fp, 10, SEEK_SET); // 定位到第10字节
fwrite("new data", 1, 8, fp); // 写入8字节新数据
fclose(fp);
}
3.3 插入内容
c
// 1. 打开源文件和临时文件
FILE *src = fopen("source.txt", "r");
FILE *tmp = fopen("temp.txt", "w");
// 2. 复制前半部分到临时文件
int pos = 0;
while(pos < insert_position) {
int ch = fgetc(src);
fputc(ch, tmp);
pos++;
}
// 3. 写入要插入的内容
fputs("New inserted content\n", tmp);
// 4. 复制剩余部分
int ch;
while((ch = fgetc(src)) != EOF) {
fputc(ch, tmp);
}
// 5. 关闭文件并替换
fclose(src);
fclose(tmp);
remove("source.txt");
rename("temp.txt", "source.txt");
4. 注意事项
- 错误处理:所有文件操作都应检查返回值
- 缓冲区刷新:必要时使用fflush()确保数据写入
- 文件锁定:在多进程/线程环境中可能需要文件锁定
- 性能考虑:频繁的小规模更新可能影响性能
5. 高级技巧
5.1 内存映射文件
对于大文件更新,可以使用内存映射提高效率:
c
#include <sys/mman.h>
// 使用mmap()函数将文件映射到内存
5.2 事务处理
实现简单的文件更新事务:
- 创建临时文件
- 执行所有更新操作
- 确认无误后替换原文件
- 出现错误则回滚
6. 实际应用场景
- 日志文件更新:追加新日志记录
- 配置文件修改:更新特定配置项
- 数据库操作:修改数据记录
- 游戏存档:更新玩家进度
总结
文件操作对于信息的储存和再利用非常重要。