前言

最近在工作中遇到了一个烦心事:我有好几个文件夹散落在硬盘各处,里面存放着大量文档、图片和备份文件,总容量超过 200GB。随着时间的推移,这些文件夹里产生了大量重复文件,不仅占用空间,还让我在查找资料时经常一头雾水。
我需要一个工具,能够指定多个文件夹,递归扫描所有子文件夹,计算每个文件的 MD5 值,找出重复文件,并且能够方便地将文件迁移到其他位置。找了一圈现成的软件,要么收费,要么功能不符合我的需求,于是我决定自己动手写一个。
今天我就来分享一下这个工具的完整开发过程,代码已经在我电脑上跑通了,效果还不错。

技术选型

我选择了 C# 和 WinForm。理由很简单:

  • 开发效率高:拖拖控件就能搭出界面原型。
  • 性能足够:处理几万个文件毫无压力。
  • 部署方便:编译成一个 exe 就能跑。

核心功能设计

在动手写代码之前,我先梳理了一下需要实现的功能:

  • 多文件夹管理:可以添加、删除要扫描的文件夹,支持拖拽添加。
  • 递归扫描:自动扫描子文件夹中的所有文件。
  • 唯一性标识:计算 MD5 值(可选 SHA256)。
  • 重复文件检测:自动高亮 MD5 相同的文件。
  • 文件列表展示:显示文件名、MD5、大小、路径等信息。
  • 文件迁移:可以将选中的文件复制到目标文件夹,支持覆盖和自动重命名。
  • 性能优化:大文件处理优化,支持取消扫描。

界面布局

我整体把界面分成两个区域:
右侧三分之二的区域为文件展示区域,左侧为选择文件夹,扫描文件等功能操作区域。
在这里插入图片描述

关键代码解析

1.文件信息模型
首先定义一个类来存储每个文件的信息:

csharp

public class FileInfoItem
{
    public string FileName { get; set; }
    public string MD5 { get; set; }
    public string SHA256 { get; set; }
    public string FileSize { get; set; }
    public string FilePath { get; set; }
    public long FileSizeBytes { get; set; }
    public string MigrationStatus { get; set; } = "未迁移";
}

2.MD5 计算(大文件优化)
计算 MD5 时有个坑:如果直接读取整个文件到内存,遇到几个 GB 的大文件就会卡死甚至内存溢出。我的解决方案是使用缓冲读取:

csharp

private string CalculateMD5(string filePath)
{
    using (var md5 = MD5.Create())
    {
        using (var stream = File.OpenRead(filePath))
        {
            // 对于大文件使用缓冲读取
            if (stream.Length > 100 * 1024 * 1024) // 大于100MB
            {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    md5.TransformBlock(buffer, 0, bytesRead, null, 0);
                }
                md5.TransformFinalBlock(new byte[0], 0, 0);
                return BitConverter.ToString(md5.Hash).Replace("-", "").ToUpper();
            }
            else
            {
                byte[] hash = md5.ComputeHash(stream);
                return BitConverter.ToString(hash).Replace("-", "").ToUpper();
            }
        }
    }
}

3.异步扫描 + 可取消操作
扫描过程可能很慢,不能让界面卡住。我用 async/await 加上 CancellationToken 来实现异步和可取消:

csharp

private async void btnScan_Click(object sender, EventArgs e)
{
    cancellationTokenSource = new CancellationTokenSource();
    
    try
    {
        await Task.Run(() => ScanFiles(cancellationTokenSource.Token));
    }
    catch (OperationCanceledException)
    {
        lblStatus.Text = "扫描已取消";
    }
}

private void ScanFiles(CancellationToken cancellationToken)
{
    foreach (string filePath in allFiles)
    {
        cancellationToken.ThrowIfCancellationRequested();
        // 计算MD5...
    }
}

4.重复文件检测
扫描完成后,按 MD5 分组就能找出重复文件:

csharp

private void HighlightDuplicateFiles()
{
    var duplicates = fileList
        .GroupBy(f => f.MD5)
        .Where(g => g.Count() > 1 && !string.IsNullOrEmpty(g.Key))
        .SelectMany(g => g)
        .ToList();

    foreach (DataGridViewRow row in dataGridViewFiles.Rows)
    {
        var item = row.DataBoundItem as FileInfoItem;
        if (item != null && duplicates.Contains(item))
        {
            row.DefaultCellStyle.BackColor = Color.LightYellow;
        }
    }
}

5.文件迁移
迁移时我提供了三种冲突处理策略:覆盖、自动重命名、询问用户:

csharp

if (File.Exists(destFileName))
{
    if (chkOverwriteExisting.Checked)
    {
        File.Copy(item.FilePath, destFileName, true);
    }
    else if (chkAutoRename.Checked)
    {
        // 自动生成不重复的文件名
        string fileNameWithoutExt = Path.GetFileNameWithoutExtension(item.FileName);
        string extension = Path.GetExtension(item.FileName);
        int counter = 1;
        while (File.Exists(destFileName))
        {
            destFileName = Path.Combine(targetPath, $"{fileNameWithoutExt}_{counter}{extension}");
            counter++;
        }
        File.Copy(item.FilePath, destFileName);
    }
    else
    {
        // 弹窗询问用户
        var result = MessageBox.Show($"文件 {item.FileName} 已存在,是否覆盖?", "确认",
            MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
        // 处理用户选择...
    }
}

使用体验

工具写好后,我拿自己的文件夹测试了一下:

  • 扫描速度:10000 个文件(总共约 50GB),计算 MD5 耗时约 25 秒
  • 重复文件检测:发现了 300 多个重复文件,都是我以前到处复制粘贴留下的
  • 迁移功能:把重复文件整理到一个新文件夹,每天打开它删一批,硬盘空间多了接近 20GB

踩过的坑

  • 开发过程中也遇到了一些问题:
  • 无权限访问的文件夹:有些系统文件夹会抛出 UnauthorizedAccessException,用 try-catch 跳过就好
  • 文件被占用:如果文件正在被其他程序打开,读取会失败,这时候可以给用户提示
  • UI 更新跨线程:在异步任务中更新界面要用 Invoke,否则会报错
  • 大文件内存问题:一开始直接 File.ReadAllBytes,扫描一个 4GB 的 ISO 文件直接内存爆炸,后来改成流式读取才解决

关于 MD5 的唯一性

有朋友问我:MD5 真的能保证文件唯一吗?
我的回答是:工程上够用,理论上有风险。MD5 的碰撞概率极低(2^128 种可能性),对于普通业务场景完全够用。但如果你处理的是安全敏感的文件(比如数字签名、法律证据),建议用 SHA256。
我在工具里预留了 SHA256 的选项,有需要可以勾上,不过计算时间会长一些。

后续可以改进的地方

  • 增量扫描:记录已扫描文件的 MD5,下次只扫描新增或修改的文件
  • 导出报告:将重复文件列表导出为 Excel 或 CSV
  • 快速预览:双击文件直接在关联程序中打开
  • 文件过滤:按扩展名、大小、修改时间过滤
  • 并行计算:用多线程同时计算多个文件的 MD5,进一步提升速度

完整代码获取

完整的项目代码我已经整理好了,包含:

  • Form1.cs - 主窗体逻辑
  • Form1.Designer.cs - 界面布局
  • Program.cs - 程序入口
  • .csproj - 项目文件
    需要的朋友可以在评论区留言,我会把代码发给大家。

写在最后

从需求分析到代码实现,这个工具断断续续写了两三天。虽然市面上有很多现成的重复文件查找工具,但自己写的用起来就是顺手 —— 想要什么功能随时加,不用看广告也不用付费。

如果你也有类似的需求,不妨试试自己动手写一个。C# + WinForm 真的是桌面小工具的快速开发利器,半天就能搭出原型,两天就能出一个能用的版本。

有什么问题欢迎在评论区交流!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐