为什么 C 拥有最好的文件 API
Source: Hacker News
2026-02-28 (Programming) (Rants)
内存映射(Memory Mapping)在 C 中的使用
有很多优秀的编程语言,但文件操作总是像事后才想到的。通常只能得到 read()、write() 以及某种序列化库。
在 C 中,你可以像访问内存中的数据一样访问文件:
#include
#include
#include
#include
#include
int main(void) {
// Create/open a file containing 1000 unsigned integers,
// initialized to all zeros.
int len = 1000 * sizeof(uint32_t);
int file = open("numbers.u32", O_RDWR | O_CREAT, 0600);
ftruncate(file, len);
// Map it into memory.
uint32_t *numbers = mmap(NULL, len,
PROT_READ | PROT_WRITE, MAP_SHARED,
file, 0);
// Do something:
printf("%d\n", numbers[42]);
numbers[42] = numbers[42] + 1;
// Clean up
munmap(numbers, len);
close(file);
return 0;
}
内存映射与加载的区别
内存映射并不等同于把文件加载到内存中:即使文件不适合放入 RAM,也能工作。数据会按需加载,所以打开一个 TB 级别的文件不会花费整天时间。它适用于所有数据类型,并且会自动缓存;如果系统需要内存,缓存会被清除。
其他语言的局限性
然而,在大多数其他语言中,你必须用 read() 读取小块数据,进行解析、处理、序列化,最后再 write() 回磁盘。这既繁琐,又不必要地限制为顺序访问——计算机已经几十年没有使用磁带了。
如果你足够幸运拥有内存映射,它通常也只能用于字节数组,仍然需要显式的解析/序列化。结果往往只是 read() 和 write() 的更好包装。
性能与开销
C 的实现并不完美:内存映射会产生开销(页面错误、TLB 刷新),而且 C 并不处理字节序问题——但它仍然比完全没有直接访问要好。
当然,你可能想做一些解析和校验,但这不应该在每次数据离开磁盘时都必须进行。内存不足的情况非常常见,这会导致无法把所有内容解析到 RAM 中。能够在不增加代码复杂度的情况下卸载数据非常有用。
安全性与序列化
只要看看 Python 的 pickle:这是一种完全不安全的序列化格式。加载一个文件甚至可能导致代码执行,即使你只想要一些数字,但它仍被广泛使用,因为它符合 Python 的“代码‑数据混合”模型。很多文件并不是不可信的。
文件系统即数据库
文件操作 同样被忽视。文件系统是最早的 NoSQL 数据库,但你通常只能得到一个包装了 C 的 readdir() 的接口。这通常导致人们在文件系统之上再跑另一个数据库,例如 SQLite,但关系型数据库从来都不完全适合你的程序。
…而且 SQL 的整合比文件更糟:除了必须序列化所有数据外,你还得用一种完全不同的语言来编写访问代码!大多数程序员最终把它当作键‑值存储来使用,并自行实现索引,结果形成了奇怪的三层嵌套数据库。
结论
所以回答标题的问题,我认为这源于一个错误的假设:认为从文件读取的数据来自别处,需要被解析;而写入磁盘的数据是要发送到别处,需要序列化成标准格式。这在内存受限的系统上根本不成立——而且在 100 GB 级别的文件面前,所有系统都是内存受限的。