直接比较大文件内容不现实,因逐字节比对耗时且占内存;应使用sha256流式计算哈希摘要(32字节),再比对摘要,并辅以首尾64kb二次校验确保准确性。

为什么直接比较文件内容不现实
大文件逐字节比对耗时且占内存,FileStream.Read 读完整个 10GB 视频再比对显然不可行。哈希的本质是把任意长度文件映射为固定长度摘要(如 SHA256 输出 32 字节),比对摘要比对原文件快几个数量级。
但要注意:哈希碰撞概率虽极低,生产环境仍需二次校验——比如摘要相同后,再用 File.ReadAllBytes 对比前 64KB + 后 64KB(跳过中间避免全读)。
选哪个哈希算法:SHA256 vs MD5 vs CRC32
MD5 速度快但已不安全,可能被刻意构造碰撞;CRC32 极快但抗碰撞性弱,只适合校验传输错误,不能用于去重判断;SHA256 是当前平衡点:.NET 内置、硬件加速支持好、碰撞概率约 2⁻¹²⁸,足够应对 PB 级存储。
- 用
SHA256.Create(),别用已标记过时的new SHA256Managed() - 流式计算必须调用
TransformFinalBlock或ComputeHash(stream),直接TransformBlock不会更新内部状态 - 哈希结果存数据库时,用
byte[]或十六进制字符串均可,但后者占空间翻倍(64 字符 vs 32 字节)
如何高效计算大文件哈希而不爆内存
核心是流式处理 + 缓冲区复用。不要把整个文件读进 byte[],而是分块喂给哈希器:
using var sha = SHA256.Create();
using var fs = File.OpenRead(filePath);
var buffer = new byte[8192]; // 复用缓冲区,避免 GC 压力
int bytesRead;
while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
sha.TransformBlock(buffer, 0, bytesRead, buffer, 0);
}
sha.TransformFinalBlock(buffer, 0, 0); // 必须调用才能输出最终结果
byte[] hash = sha.Hash;
注意:TransformBlock 不会自动累加,漏掉 TransformFinalBlock 会导致哈希值错误;若用 ComputeHash(fs) 更简洁,但内部仍会分配临时缓冲区,对超高频小文件场景可考虑手动流式控制。
去重逻辑怎么嵌入实际存储流程
典型路径是「上传 → 计算哈希 → 查库 → 决策」。关键点不在哈希本身,而在并发和原子性:
- 数据库查哈希时,用
SELECT id FROM files WHERE hash = @hash,别先SELECT COUNT(*)再SELECT id,避免竞态 - 如果查不到,插入新记录时必须加唯一约束(
UNIQUE INDEX ON hash),否则并发上传同一文件可能写入多条 - 物理文件名建议用哈希值(如
/data/9f86d081.../chunk001),而非自增 ID,便于跨节点共享去重结果 - 小文件(
真正难的不是算哈希,而是当多个线程同时发现某个哈希不存在、都准备写入时,谁该真正落盘、谁该回退——这得靠数据库唯一索引兜底,代码里别自己加锁模拟。










