如何用 MD5 哈希做文件去重
为什么用哈希而非逐字节比较
要判断两个文件内容是否完全相同,最直接的方法是逐字节比较,但这对于大文件或大量文件效率极低。哈希比较的优势在于:只需对每个文件计算一次哈希(O(n),n 为文件大小),然后比较哈希值(O(1))即可。一旦所有文件都有了哈希值,相同哈希值的文件就是内容相同的文件。MD5 在这个场景中是一个很好的选择:速度快,128 位的哈希空间对于实际的文件去重来说,偶然碰撞的概率可以忽略不计。
基础文件去重脚本(Python)
import hashlib, os
from collections import defaultdict
def md5_file(filepath, chunk_size=65536):
"""Calculate MD5 of a file efficiently"""
hasher = hashlib.md5()
with open(filepath, 'rb') as f:
while chunk := f.read(chunk_size):
hasher.update(chunk)
return hasher.hexdigest()
def find_duplicates(directory):
"""Find all duplicate files in a directory"""
hash_map = defaultdict(list)
for root, dirs, files in os.walk(directory):
for filename in files:
filepath = os.path.join(root, filename)
try:
file_hash = md5_file(filepath)
hash_map[file_hash].append(filepath)
except (IOError, PermissionError):
pass
# Return only groups with duplicates
return {h: paths for h, paths in hash_map.items() if len(paths) > 1}
# Usage
duplicates = find_duplicates('/path/to/folder')
for hash_val, paths in duplicates.items():
print(f"\nDuplicate group ({hash_val[:8]}...):")
for path in paths:
print(f" {path}")
优化:先比较文件大小
一个重要优化是先按文件大小分组——只有大小相同的文件才可能是内容重复的文件,不需要对大小不同的文件计算哈希:
def find_duplicates_optimized(directory):
# Step 1: Group by file size (fast - no hashing)
size_map = defaultdict(list)
for root, _, files in os.walk(directory):
for filename in files:
filepath = os.path.join(root, filename)
try:
size = os.path.getsize(filepath)
size_map[size].append(filepath)
except OSError:
pass
# Step 2: Only hash files where size matches
hash_map = defaultdict(list)
for size, paths in size_map.items():
if len(paths) 1}
在数据库中存储文件哈希
对于持续运行的系统(如文件存储服务),将文件哈希存储在数据库中可以避免重复计算,实现即时去重:
-- Database schema for deduplication
CREATE TABLE files (
id INTEGER PRIMARY KEY,
md5_hash CHAR(32) NOT NULL,
sha256_hash CHAR(64), -- Optional extra verification
file_size BIGINT NOT NULL,
original_name TEXT,
storage_path TEXT NOT NULL, -- Actual storage location
created_at TIMESTAMP DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_md5 ON files(md5_hash);
-- When uploading a new file:
-- 1. Calculate MD5
-- 2. Check if MD5 already exists in DB
-- 3. If yes: return existing storage_path (dedup!)
-- 4. If no: store file, insert new row
内容寻址存储(CAS)的原理
内容寻址存储(CAS)是一种高级去重架构:文件不按名称存储,而是按其内容哈希存储。文件名就是其哈希值。Git 的对象存储就是一种 CAS 实现——每个 Blob 对象的文件名就是其内容的 SHA1 哈希(现已迁移至 SHA256)。在 CAS 中,相同内容的文件只会被存储一次,但可以被多个不同名称引用,天然实现了去重。
# CAS directory structure example
storage/
ab/
ab1234567890abcdef1234567890abcdef # actual content
5d/
5d41402abc4b2a76b9719d911017c592 # actual content
...
# Lookup: hash → file path
def get_file_path(md5_hash):
return f"storage/{md5_hash[:2]}/{md5_hash}"
大规模文件去重的注意事项
- **MD5 碰撞风险:**在真实的大量文件去重中,MD5 随机碰撞的概率极低,但如果处理的是由用户提交的文件,存在被恶意构造碰撞的风险。关键数据可以追加 SHA256 二次验证
- **大文件处理:**按块读取(如 64KB 块)避免内存问题
- **并行计算:**使用多线程或多进程并行哈希多个文件
- **增量去重:**记录已哈希文件的修改时间,避免对未变化文件重新计算
立即免费使用相关工具
免费使用 →