检测重复文件

时间:2012-03-21 15:51:01

标签: algorithm duplicates hash

我想检测目录树中的重复文件。如果找到两个相同的文件,则只保留其中一个副本,并删除剩余的重复项以节省磁盘空间。

重复意味着文件名和路径可能不同的内容相同的文件。

我正在考虑为此目的使用哈希算法但是有可能不同的文件具有相同的哈希值,所以我需要一些额外的机制来告诉我即使哈希值是相同的,文件也不一样因为我不想删除两个不同的文件。

您会使用哪种额外快速可靠的机制?

7 个答案:

答案 0 :(得分:15)

计算哈希将使您的程序运行缓慢。您还可以检查文件大小。所有重复文件应具有相同的文件大小。如果他们共享相同的文件大小应用哈希检查。它会让你的程序表现得很快。

可以有更多步骤。

  1. 检查文件大小是否相等
  2. 如果步骤1通过,请检查第一个和最后一个字节范围(比如100个字节)是否相等
  3. 如果第2步过去,请检查文件类型
  4. 如果第3步过去,请检查哈希最后
  5. 您添加的条件越多,它的执行速度就越快,您就可以避免这种方式(哈希)。

答案 1 :(得分:2)

这取决于您要比较的文件。

A)最糟糕的情况是:

  1. 你有很多大小相同的文件
  2. 文件非常大
  3. 这些文件与文件
  4. 中较窄的随机位置的差异非常相似

    例如,如果你有:

    • 相同尺寸的100x 2MB文件,
    • 相互比较,
    • 使用二进制比较
    • 50%文件读取(在文件的前半部分找到不等字节的概率)

    然后你会:

    • 10,000比较
    • 1MB等于
    • 总共10GB的阅读量。

    但是,如果你有相同的场景,但首先导出文件的哈希,你会:

    • 从磁盘(通常是计算机中最慢的组件)中读取200MB数据,然后提取
    • 内存1.6K (使用MD5哈希--16字节 - 安全性并不重要)
    • 并读取2N * 2MB进行最终的直接二进制比较,其中N是找到的重复数。

    我认为最糟糕的情况是不典型

    B)典型的案例场景是:

    1. 文件大小通常不同
    2. 文件很可能在文件开头附近不同 - 这意味着直接二进制比较通常不会涉及读取大量相同大小的不同文件上的整个文件。
    3. 例如,如果你有:

      • MP3文件的文件夹(它们不会太大 - 可能不超过5MB)
      • 100个文件
      • 首先检查尺寸
      • 最多3个相同尺寸的文件(重复或不重复)
      • 二进制比较用于相同尺寸的文件
      • 在1KBytes之后可能有所不同

      然后你会:

      • 最多33个案例,其中长度在3个文件集中相同
      • 以4K块的形式同时并行读取3个文件(或更多个文件)
      • 找到0%重复项 - 33 * 3 * 4K读取文件= 396KB磁盘读数
      • 找到100%乘法= 33 * 3 * N,其中N是文件大小(~5MB)= ~495MB

      如果您期望100%倍数,则散列不会比直接二进制比较更有效。鉴于您应该期望< 100%倍数,散列效率将低于直接二进制比较。

      C)重复比较

      这是例外。为所有文件构建散列+长度+路径数据库将加速重复比较。但好处是微不足道的。它最初需要100%读取文件并存储哈希数据库。新文件需要100%读取然后添加到数据库中,如果匹配,仍然需要直接二进制比较作为比较的最后一步(以排除哈希冲突)。即使大多数文件的大小不同,当在目标文件夹中创建新文件时,它可能与现有文件大小匹配,并且可以快速排除在直接比较之外。

      总结:

      • 不应使用额外的哈希值(最终测试 - 二进制比较 - 应该始终是最终测试)
      • 当存在许多不同大小的文件时,二进制比较通常在首次运行时更有效
      • MP3比较适用于长度比二进制比较。

答案 2 :(得分:1)

哈希解决方案很好 - 您只需要执行一个冲突机制来处理两个散列为相同值的元素。 [chainingopen addressing]。

只是迭代地添加元素 - 如果您的实现检测到存在欺骗 - 它将不会将其添加到哈希集。 如果在尝试添加元素后未更改集的大小,您将知道元素是欺骗。

很可能已经在您的语言中实现了这种数据结构 - 例如java中的HashSet和C ++中的unordered_set

答案 3 :(得分:1)

如果您使用SHA-1或更好的SHA-256或更高版本的哈希算法,我真的怀疑您是否会为两个不同的文件获得相同的哈希值。 SHA是一种加密哈希函数,用于Git等版本控制系统。所以你可以放心,它会为你完成这项工作。

但如果您仍需要进行额外检查,可以按照以下两个步骤进行操作 1)解析标题 - 这是一个非常难以破解的cookie,因为不同的格式可能有不同的标题长度
2)进行一些健全性检查 - 文件大小,读取随机文件位置并尝试检查它们是否相同

答案 4 :(得分:1)

这是md5sum的典型输出:

0c9990e3d02f33d1ea2d63afb3f17c71

如果你不必担心故意伪造文件,那么第二个随机文件匹配的可能性是

1/(decimal(0xfffffffffffffffffffffffffffffff)+1)

如果您将文件大小考虑为额外的测试,那么您的确定性会增加,两个文件都适合。您可能会添加越来越多的测量值,但在这样的辩论中,逐位比较将成为最后一个词。出于实际目的,md5sum应该足够了。

答案 5 :(得分:0)

/// ------------------------------------------------------------------------------------------------------------------------
    /// <summary>
    /// Writes duplicate files to a List<String>
    /// </summary>
    private void CompareDirectory(string[] files)
    {
        for (int i = 0; i < files.Length; i++)
        {
            FileInfo one = new FileInfo(files[i]); // Here's a spot for a progressbar or something

            for (int i2 = 0; i2 < files.Length; i2++) 
            {
                if (i != i2 && !duplicatePathsOne.Contains(files[i2])) // In order to prevent duplicate entries
                {
                    FileInfo two = new FileInfo(files[i2]);
                    if (FilesAreEqual_OneByte(one, two))
                    {
                        duplicatePathsOne.Add(files[i]);
                        duplicateNamesOne.Add(Path.GetFileName(files[i]));
                        duplicatePathsTwo.Add(files[i2]);
                        duplicateNamesTwo.Add(Path.GetFileName(files[i2]));
                    }
                }
            }
        }
    }


/// ------------------------------------------------------------------------------------------------------------------------
    /// <summary>
    /// Compares files by binary
    /// </summary>
    private static bool FilesAreEqual_OneByte(FileInfo first, FileInfo second)
    {
        if (first.Length != second.Length)
            return false;

        using (FileStream fs1 = first.OpenRead())
        using (FileStream fs2 = second.OpenRead())
        {
            for (int i = 0; i < first.Length; i++)
            {
                if (fs1.ReadByte() != fs2.ReadByte())
                    return false;
            }
        }

        return true;
    }

答案 6 :(得分:0)

这是一个VBS脚本,它将生成CSV文件,以根据MD5文件校验和和文件大小显示文件夹中的重复文件。

Set fso = CreateObject("Scripting.FileSystemObject")
Dim dic: Set dic = CreateObject("Scripting.Dictionary")
Dim oMD5:  Set oMD5 = CreateObject("System.Security.Cryptography.MD5CryptoServiceProvider")
Dim oLog 'As Scripting.TextStream

Set oArgs = WScript.Arguments

If oArgs.Count = 1 Then
    sFolderPath = GetFolderPath()
    Set oLog = fso.CreateTextFile(sFolderPath & "\DublicateFiles.csv", True)
    oLog.Write "sep=" & vbTab & vbCrLf
    CheckFolder oArgs(I)
    oLog.Close
    Msgbox "Done!"
Else
    Msgbox "Drop Folder"
End If

Sub CheckFolder(sFolderPath)
    Dim sKey
    Dim oFolder 'As Scripting.Folder
    Set oFolder = fso.GetFolder(sFolderPath)

    For Each oFile In oFolder.Files
        'sKey = oFile.Name & " - " & oFile.Size
        sKey = GetMd5(oFile.Path) & " - " & oFile.Size

        If dic.Exists(sKey) = False Then 
            dic.Add sKey, oFile.Path
        Else
            oLog.Write oFile.Path & vbTab & dic(sKey) & vbCrLf
        End If
    Next

    For Each oChildFolder In oFolder.SubFolders
        CheckFolder oChildFolder.Path
    Next
End Sub

Function GetFolderPath()
    Dim oFile 'As Scripting.File
    Set oFile = fso.GetFile(WScript.ScriptFullName)
    GetFolderPath = oFile.ParentFolder
End Function

Function GetMd5(filename)
    Dim oXml, oElement

    oMD5.ComputeHash_2(GetBinaryFile(filename))

    Set oXml = CreateObject("MSXML2.DOMDocument")
    Set oElement = oXml.CreateElement("tmp")
    oElement.DataType = "bin.hex"
    oElement.NodeTypedValue = oMD5.Hash
    GetMd5 = oElement.Text
End Function

Function GetBinaryFile(filename)
    Dim oStream: Set oStream = CreateObject("ADODB.Stream")
    oStream.Type = 1 'adTypeBinary
    oStream.Open
    oStream.LoadFromFile filename
    GetBinaryFile= oStream.Read
    oStream.Close
    Set oStream = Nothing
End Function

这是一个VBS脚本,它将生成CSV文件,以根据文件名和大小显示文件夹中的重复文件。

Set fso = CreateObject("Scripting.FileSystemObject")
Dim dic: Set dic = CreateObject("Scripting.Dictionary")
Dim oLog 'As Scripting.TextStream

Set oArgs = WScript.Arguments

If oArgs.Count = 1 Then
    sFolderPath = GetFolderPath()
    Set oLog = fso.CreateTextFile(sFolderPath & "\DublicateFiles.csv", True)
    oLog.Write "sep=" & vbTab & vbCrLf
    CheckFolder oArgs(I)
    oLog.Close
    Msgbox "Done!"
Else
    Msgbox "Drop Folder"
End If

Sub CheckFolder(sFolderPath)
    Dim sKey
    Dim oFolder 'As Scripting.Folder
    Set oFolder = fso.GetFolder(sFolderPath)

    For Each oFile In oFolder.Files
        sKey = oFile.Name & " - " & oFile.Size

        If dic.Exists(sKey) = False Then 
            dic.Add sKey, oFile.Path
        Else
            oLog.Write oFile.Path & vbTab & dic(sKey) & vbCrLf
        End If
    Next

    For Each oChildFolder In oFolder.SubFolders
        CheckFolder oChildFolder.Path
    Next
End Sub

Function GetFolderPath()
    Dim oFile 'As Scripting.File
    Set oFile = fso.GetFile(WScript.ScriptFullName)
    GetFolderPath = oFile.ParentFolder
End Function