← 返回博客

如何用 MD5 哈希做文件去重

2026-04-17 · 5 分钟阅读

为什么用哈希而非逐字节比较

要判断两个文件内容是否完全相同,最直接的方法是逐字节比较,但这对于大文件或大量文件效率极低。哈希比较的优势在于:只需对每个文件计算一次哈希(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}"

大规模文件去重的注意事项

立即免费使用相关工具

免费使用 →