我给自己写了一个本地重复文件查找器:先找准,再决定删不删

举报
yd_294377644 发表于 2026/11/03 16:47:30 2026/11/03
【摘要】 硬盘里的下载、照片和项目备份越积越多,同一文件常被复制到不同目录。我不敢用“按文件名删除”的工具,于是用 Python 写了一个只负责发现和确认的本地重复文件查找器。这篇博客记录从分组扫描、分段哈希到结果核验的实现过程,也总结大文件性能、软链接和误删防护等细节。

目录

  1. 缘起:硬盘空间到底被谁吃掉了
  2. 需求拆解:找重复文件不等于自动删除
  3. 技术选型:标准库已经足够
  4. 核心实现一:用文件大小缩小范围
  5. 核心实现二:分段哈希与完整确认
  6. UI 定制:把风险放在操作之前
  7. 三个扫描文件时容易忽略的坑
  8. 结尾:整理文件最重要的是可确认

1. 缘起:硬盘空间到底被谁吃掉了

我的硬盘总是在不知不觉中变满。真正占空间的往往不是正在使用的项目,而是散落在不同目录里的副本:下载过两次的安装包、从手机重复导出的照片、改过目录名后又完整复制一份的项目备份。

手动寻找重复文件几乎不现实:

  • 文件名相同,不代表内容相同;
  • 文件名不同,也可能是同一个文件;
  • 照片和视频数量太多,不可能逐个打开比较;
  • 一旦跨越多个目录,人很难记住哪个版本已经看过。

我试过一些重复文件清理软件,但“扫描完成后一键删除”的设计反而让我不放心。对我来说,工具可以帮忙找,最终删不删必须由人确认。

因此我给这个工具定下了第一条原则:只判断内容是否相同,不根据文件名猜测;默认只展示结果,不自动删除任何文件。

2. 需求拆解:找重复文件不等于自动删除

把需求写成可检查的清单后,工具的边界就清楚了:

诉求(人话) 拆解后(可验收)
找出真正相同的文件 最终使用完整 SHA-256 校验
扫描别太慢 先按大小分组,再做分段哈希
大文件别把内存撑满 流式读取,每次只读固定大小数据块
看懂重复在哪里 按组展示路径、大小和修改时间
别误删 默认无删除按钮,只支持打开所在目录
下次继续看 可导出 JSON 或 CSV 扫描报告

我没有加入“相似图片识别”。两张压缩程度不同但画面相同的照片,需要感知哈希或图像模型判断,这和“字节完全相同”是两类问题。把它硬塞进第一个版本,只会让结果变得难以解释。

心得:文件整理工具必须先定义“重复”的含义。我的定义很简单——文件内容逐字节一致。

3. 技术选型:标准库已经足够

工具最终使用 Python 编写,主要能力全部来自标准库:

能力 方案
遍历目录 pathlib.Path.rglob()
读取文件信息 Path.stat()
内容摘要 hashlib.sha256()
并发计算 concurrent.futures
导出报告 json 与 csv
简单界面 tkinter

这里没有数据库。一次扫描的结果本质上是一组路径和摘要,保存在内存中即可;需要留档时再导出成普通文件。

我也没有一开始就做多进程。重复文件扫描通常受磁盘读取速度限制,同时让太多任务读取同一块机械硬盘,反而会因为频繁寻道变慢。默认使用少量线程已经足够,线程数可以根据存储设备再调整。

心得:性能优化要先判断瓶颈在哪里。磁盘任务不是线程越多越快。

4. 核心实现一:用文件大小缩小范围

如果两个文件内容完全相同,它们的字节数一定相同。因此第一步根本不用读取内容,只需要按文件大小分组。

from collections import defaultdict
from pathlib import Path

def group_by_size(root: Path):
    groups = defaultdict(list)

    for path in root.rglob("*"):
        if not path.is_file():
            continue

        try:
            groups[path.stat().st_size].append(path)
        except OSError:
            continue

    return {
        size: paths
        for size, paths in groups.items()
        if len(paths) > 1
    }

这一步过滤效果非常明显。一个包含几万个文件的目录里,真正大小完全一致的文件通常只占一小部分。只有进入候选组的文件,才值得继续读取内容。

我还加了两个简单过滤条件:

  • 跳过大小为 0 的空文件,因为它们内容天然相同,却没有清理价值;
  • 允许设置最小文件大小,例如只检查 1 MB 以上文件,把时间花在真正占空间的对象上。

扫描时界面会分别显示“已发现文件数”和“待校验候选数”。这样用户能知道程序正在缩小范围,而不是误以为它卡在某个目录。

心得:能用元数据排除的对象,就不要急着读取文件内容。

5. 核心实现二:分段哈希与完整确认

大小相同只是候选条件。为了减少不必要的完整读取,我先计算文件头部和尾部的分段哈希,再对仍然一致的候选计算完整 SHA-256。

import hashlib

SAMPLE_SIZE = 64 * 1024

def sample_hash(path):
    size = path.stat().st_size
    digest = hashlib.sha256()

    with path.open("rb") as file:
        digest.update(file.read(SAMPLE_SIZE))

        if size > SAMPLE_SIZE:
            file.seek(max(0, size - SAMPLE_SIZE))
            digest.update(file.read(SAMPLE_SIZE))

    return digest.hexdigest()

完整哈希采用流式读取,避免把大文件一次性放进内存:

def full_hash(path, chunk_size=1024 * 1024):
    digest = hashlib.sha256()

    with path.open("rb") as file:
        while chunk := file.read(chunk_size):
            digest.update(chunk)

    return digest.hexdigest()

最终的处理顺序是:

  1. 按文件大小分组;
  2. 对同大小文件计算分段哈希;
  3. 对分段哈希相同的文件计算完整哈希;
  4. 只有完整哈希一致,才归入同一个重复组。

分段哈希不是最终证据,它只是又一层便宜的过滤。真正显示给用户的重复组,必须经过完整摘要确认。

心得:高效扫描不是降低准确性,而是把昂贵校验留给最有可能重复的文件。

6. UI 定制:把风险放在操作之前

界面被我分成三个区域:顶部选择扫描目录和最小文件大小,中间显示进度,底部按组展示重复结果。

每个重复组会显示:

  • 文件数量和单个文件大小;
  • 这组副本理论上可释放的空间;
  • 每个文件的完整路径、修改时间和只读状态;
  • “打开文件”和“打开所在目录”两个安全操作。

我故意没有在第一版加入“一键删除重复项”。扫描结果可能包含用户主动保留的不同目录备份,也可能包含某些软件依赖的副本。字节相同只能证明内容相同,不能证明其中一个没有用途。

导出的报告会保存扫描时间、根目录、文件大小和完整哈希。用户可以先查看报告,再使用熟悉的文件管理方式处理。工具本身不替用户做不可逆决定。

心得:越接近删除操作,界面越应该提供证据,而不是提供捷径。

7. 三个扫描文件时容易忽略的坑

坑一:扫描过程中,文件可能正在变化。 日志、数据库和下载中的文件会边扫描边增长。如果分段哈希和完整哈希之间文件大小发生变化,本次结果就不再可信。我的做法是在完整校验前后各读取一次文件信息,只要大小或修改时间变化,就把该文件标记为“扫描期间已变化”并跳过。

坑二:符号链接可能造成重复遍历。 目录链接有机会把扫描带回已经访问过的位置,甚至形成循环。工具默认不跟随目录符号链接,并记录跳过数量。是否跟随必须由用户明确开启。

坑三:访问失败不等于文件不存在。 权限不足、文件被占用或路径过长都可能让读取失败。程序不应该悄悄把这些对象当作“非重复”,而要在扫描结束后列出失败路径和原因,让结果的覆盖范围清清楚楚。

另外,哈希碰撞在理论上存在。对于普通本地整理,SHA-256 已经足够可靠;如果处理的是必须逐字节证明一致的高价值数据,还可以在哈希相同后再做二进制逐块比较。

8. 结尾:整理文件最重要的是可确认

写完这个工具后,我没有立刻多出大量可用空间,反而先看清了自己的文件习惯:下载目录里堆着重复安装包,照片在不同备份目录中交叉出现,旧项目压缩包又被复制进了新归档。

重复文件查找器真正有价值的地方,不是替我按下删除键,而是把原本藏在硬盘各处的关系整理出来:

  1. 哪些文件内容完全相同;
  2. 它们分别位于什么位置;
  3. 每组副本占用了多少空间;
  4. 哪些路径因权限或变化没有完成校验。

有了这些信息,删除才是一个可以判断的动作,而不是一次赌博。

如果你也想写一个重复文件查找器,建议从“按大小分组 + 完整哈希”开始。数据量不大时,这已经足够。只有确认扫描确实慢,再增加分段哈希和有限并发,不要一开始就把流程设计得过于复杂。

文件整理的目标不是让目录看起来干净,而是让每一次处理都有依据、每一个结果都能解释。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。