应使用 md5.create() 配合 file.openread() 流式计算文件 md5,避免 file.readallbytes() 导致内存溢出;需前置检查文件存在性与权限,哈希结果统一转小写无分隔符字符串;比对文件先比长度再比哈希;多线程下每个线程必须创建独立 md5 实例。

直接用 MD5.Create() 配合 File.OpenRead() 流式计算,别碰 File.ReadAllBytes()——大文件一读就崩内存,不是警告是事实。
为什么不能用 ReadAllBytes 计算文件 MD5
因为 File.ReadAllBytes() 会把整个文件一次性加载进托管堆,1GB 文件 ≈ 1GB byte[],.NET 要求连续内存块,实际可能触发 OutOfMemoryException,尤其在 32 位进程或低内存服务器上。这不是“可能”,而是“大概率”。MD5.ComputeHash(Stream) 内部用默认 4KB 缓冲分块读取,内存占用恒定,这才是生产可用的方式。
常见错误写法:
// ❌ 危险:小文件能跑,上线后某天突然崩 byte[] bytes = File.ReadAllBytes(path); using var md5 = MD5.Create(); byte[] hash = md5.ComputeHash(bytes);
正确姿势只有一条:永远走流式。
如何安全地计算单个文件的 MD5 字符串
核心是资源自动释放 + 格式统一。用 using 确保 FileStream 和 MD5 实例及时释放,哈希结果转字符串时用 BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(),避免大小写或分隔符不一致导致比对失败。
-
File.Exists(path)必须前置检查,否则File.OpenRead()抛FileNotFoundException,不是返回 null - 路径权限不足、文件正被记事本/杀软/备份工具占用,都会抛
IOException,建议捕获并提示“文件可能被其他程序锁定” - 不要用
ToString()或==比较两个哈希字符串——BitConverter输出带短横,手动拼接可能大小写混用,一律转小写再比
对比两个文件是否完全相同,光比 MD5 不够
先比长度,再比哈希。因为两个不同内容的文件理论上可能产生相同 MD5(碰撞),概率极低但非零;而长度不同,100% 不同。跳过长度检查等于主动放弃一层廉价且确定的过滤。
- 用
new FileInfo(path1).Length != new FileInfo(path2).Length判断,比打开流快得多 - 长度相等再分别计算 MD5,得到两个
byte[]后用SequenceEqual()比较,比字符串比较更快、更准、不区分大小写 - 如果服务端提供的是 SHA256 值,客户端却算 MD5,结果必然不等——算法必须严格匹配
多线程并发计算多个文件 MD5 的坑
每个线程必须创建独立的 MD5 实例。别用 static readonly MD5 s_md5 = MD5.Create(); 这种缓存方式——MD5 不是线程安全的,共用实例会导致哈希值错乱,现象是:99% 时间结果正常,压测或高峰时段偶发校验失败,极难复现和定位。
正确写法:
Parallel.ForEach(filePaths, path =>
{
using var md5 = MD5.Create(); // ✅ 每次都新实例
using var fs = File.OpenRead(path);
byte[] hash = md5.ComputeHash(fs);
// ...处理结果
});
流式计算本身不阻塞,但磁盘 I/O 是瓶颈,真正要注意的是文件句柄数上限和并发打开太多文件引发的系统级限制,而不是 CPU 或内存。










