因为内存扛不住,20gb文件直接new会触发std::bad_alloc;即使成功,缓存失效和页交换也会使性能骤降十倍以上。

为什么不能直接用 std::ifstream::read 一次性读整个大文件?
因为内存扛不住。一个 20GB 的日志文件,new char[20ULL * 1024 * 1024 * 1024] 直接触发 std::bad_alloc;就算不崩,也会严重拖慢系统响应。MD5 本身是分块迭代的算法(512-bit / 64-byte 分组),天然支持流式更新,关键在于别打断哈希状态链。
用 OpenSSL 的 EVP_MD_CTX 增量喂数据最稳
标准库没提供增量 MD5 接口,手写容易出错(比如字节序、填充逻辑、final 处理)。OpenSSL 的 EVP 接口封装了全部细节,且跨平台可靠。注意三点:
-
EVP_MD_CTX_new()后必须配对EVP_MD_CTX_free(),否则泄漏内部堆内存 - 每次
EVP_DigestUpdate()可传任意长度数据(哪怕 1 字节),内部会缓存未满块 - 最终
EVP_DigestFinal_ex()才真正补 padding 并输出 16 字节摘要,之前调EVP_DigestFinal_ex()会清空上下文
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_md5(), nullptr);
// 循环读文件块
while ((n = fread(buf, 1, BUFSIZ, fp)) > 0) {
EVP_DigestUpdate(ctx, buf, n);
}
unsigned char md[MD5_DIGEST_LENGTH];
EVP_DigestFinal_ex(ctx, md, &len); // 此时才计算最终值
EVP_MD_CTX_free(ctx);
缓冲区大小选 8192 还是 65536?看 I/O 路径
太小(如 512)导致系统调用频繁,CPU 花在上下文切换上;太大(如 1MB)可能挤占其他线程缓存,且单次 read() 不保证填满。实测在 ext4 + SSD 上,BUFSIZ(通常 8192)和 65536 性能差异不到 3%,但 65536 在某些 NFS 或容器挂载场景下反而因内核页缓存压力升高而变慢。建议:
- 本地磁盘:用
65536(64KB),接近多数文件系统默认块大小 - 网络存储或不确定环境:回落到
8192,更保守 - 绝对别用
std::vector<char>(filesize)</char>预分配——这是典型反模式
遇到 errno == EINTR 或读取中断怎么办?
流式计算常跑在后台服务里,fread() 或 read() 可能被信号打断,返回值为 0 但 errno == EINTR。此时不能当文件结束处理,必须重试。OpenSSL 自身不处理系统调用中断,得在外层兜底:
- 用
read()+while (n == -1 && errno == EINTR)循环重试,比fread()更可控 - 若用 C++ iostream,
ifs.read(buf, size)后检查ifs.gcount() == 0 && !ifs.eof(),再结合errno判断是否需重试 - 不要忽略
partial read:即使没中断,read()也可能只返回部分字节(尤其管道、socket),必须用循环确保读满预期长度
流式 MD5 看似简单,但边界条件全在 I/O 层——哈希逻辑本身很干净,真正掉坑里的都是 read() 返回值没吃透、EVP_MD_CTX 生命周期搞混、或者把“读完”和“读成功”当成一回事。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











