一、文件的基本概念
在Linux中,文件是存放在磁盘(或其他存储介质)上的一段数据的集合。从操作系统的角度看,一切皆文件,包括普通文件、目录、设备、管道等。我们这里讨论的是普通文件,即存储在文件系统中的数据流。
二、文件操作的三个核心步骤
无论采用何种编程语言,文件操作通常都遵循"三步走"原则:
- 打开文件 -- 建立程序与文件之间的连接,获得一个文件流指针。
- 读写文件 -- 根据需求读取或写入数据。
- 关闭文件 -- 释放资源,确保数据完整写入磁盘。
三、文件类型:ASCII码文件与二进制文件
- ASCII码文件(文本文件):文件中的每个字符都是可显示的ASCII字符,例如源代码、配置文件、日志等。这类文件可以用文本编辑器直接查看。
- 二进制文件 :文件中包含不可显示的字符(如
0x00、0xFF等),例如图片、音视频、可执行程序、压缩包等。
本质上,所有文件都是二进制存储的,ASCII码文件只是二进制文件的一种特殊形式,但编程时我们根据内容格式选择不同的读写方式。
四、常用文件IO函数详解
标准C库(stdio.h)提供了一套跨平台的IO接口,在Linux下同样适用。以下逐个介绍最常用的函数。
4.1 fopen -- 打开文件
c
FILE *fopen(const char *pathname, const char *mode);
- 功能 :打开
pathname指定的文件,并建立一个与程序关联的流(stream)。 - 参数 :
pathname:文件路径(绝对或相对)。mode:打开方式,常用模式如下:
| 模式 | 含义 |
|---|---|
"r" |
只读,文件必须存在,否则返回NULL。 |
"r+" |
读写,文件必须存在。 |
"w" |
只写,文件不存在则创建,存在则清空(长度截为0)。 |
"w+" |
读写,文件不存在则创建,存在则清空。 |
"a" |
追加只写,文件不存在则创建,写入数据追加到文件末尾。 |
"a+" |
追加读写,文件不存在则创建,读可从任意位置,写只能追加到末尾。 |
- 返回值 :成功返回
FILE*指针,失败返回NULL,并设置errno。
注意 :程序启动时,系统会自动打开三个标准流,无需手动fopen:
stdin-- 标准输入(键盘)stdout-- 标准输出(终端)stderr-- 标准错误(终端)
4.2 fclose -- 关闭文件
c
int fclose(FILE *stream);
- 功能 :关闭
stream指向的流,刷新缓冲区,释放相关资源。 - 返回值 :成功返回
0,失败返回EOF(-1)。
重要 :文件操作完毕后务必调用
fclose,否则可能导致数据丢失或资源泄漏。
五、标准IO的缓存机制
标准IO库为了提高效率,会为每个流分配缓冲区。根据刷新条件的不同,缓存分为三类:
| 缓存类型 | 大小 | 刷新条件 |
|---|---|---|
| 全缓存 | 通常4KB | ①缓冲区满;②调用fflush;③程序正常结束或fclose。 |
| 行缓存 | 通常1KB | ①缓冲区满;②遇到换行符\n;③调用fflush;④程序结束或fclose。 |
| 不缓存 | 0(立即刷新) | 每次输出都立即刷新(如stderr),用于实时错误信息。 |
- 与磁盘文件 建立的流默认为全缓存。
- 与终端 (
stdout)建立的流默认为行缓存。 stderr默认为不缓存,保证错误信息及时显示。
我们可以使用fflush(FILE *stream)手动刷新缓冲区,强制将数据写入内核。
六、字符级别的读写:fputc 和 fgetc
6.1 fputc -- 写入一个字符
c
int fputc(int c, FILE *stream);
- 功能 :将字符
c写入stream指定的流中。 - 参数 :
c为要写入的字符(转换为int),stream为目标流。 - 返回值 :成功返回写入字符的ASCII码值,失败返回
EOF。
6.2 fgetc -- 读取一个字符
c
int fgetc(FILE *stream);
- 功能 :从
stream中读取一个字符。 - 返回值 :成功返回读取字符的ASCII码值,失败或读到文件末尾返回
EOF。
示例:统计文件行数
下面是一个完整的程序,用于统计指定文件的行数(换行符\n的数量):
c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
perror("fopen");
exit(EXIT_FAILURE);
}
int lines = 0;
int ch;
while ((ch = fgetc(fp)) != EOF) {
if (ch == '\n') {
lines++;
}
}
fclose(fp);
printf("File '%s' has %d lines.\n", argv[1], lines);
return 0;
}
编译运行:
bash
gcc -o line_count line_count.c
./line_count /etc/passwd
七、更多读写函数(简要介绍)
除了字符级别的fputc/fgetc,标准库还提供了行、格式化和二进制级别的读写函数,在实际开发中更为常用:
- 行读写 :
fputs/fgets-- 读写一行字符串(处理换行符)。 - 格式化读写 :
fprintf/fscanf-- 按指定格式读写数据,类似printf/scanf但作用于文件。 - 二进制读写 :
fwrite/fread-- 直接读写内存块,适用于结构体、数组等二进制数据。
八、总结
| 函数 | 作用 | 典型应用场景 |
|---|---|---|
| fopen | 打开文件 | 建立文件流 |
| fclose | 关闭文件 | 释放资源,保证数据落盘 |
| fputc | 写一个字符 | 逐字符输出,如生成文本 |
| fgetc | 读一个字符 | 逐字符解析,如词法分析 |
| fputs | 写字符串 | 按行写入配置或日志 |
| fgets | 读字符串 | 按行读取配置文件 |
| fprintf | 格式化写入 | 输出带格式的数据 |
| fscanf | 格式化读取 | 解析结构化文本 |
| fwrite | 二进制写入 | 保存结构体、数组到文件 |
| fread | 二进制读取 | 从文件恢复结构体、数组 |
掌握这些函数,就能应对绝大多数文件操作需求。记住三个要点:打开 → 读写 → 关闭,并留意缓冲区刷新,避免数据丢失。