比较大文件的内容

时间:2012-08-24 21:09:54

标签: c# binary compare large-files

我需要比较非常大的文件的内容。程序的速度很重要。我需要100%匹配。我阅读了很多信息,但没有找到最佳解决方案。我正在考虑两个选择和两个问题。

  1. 逐字节比较整个文件 - 对于大文件来说不够快。
  2. 使用哈希进行文件比较 - 不是100%匹配具有相同哈希值的两个文件。
  3. 你会建议什么?也许我可以利用线程? MemoryMappedFile可以提供帮助吗?

7 个答案:

答案 0 :(得分:9)

如果您真的需要100%保证文件100%相同,那么您需要进行逐字节比较。这只是问题所在 - 唯一具有0%错误匹配风险的散列方法是身份函数!

我们留下的是快捷方式,可以快速给我们快速答案,让我们跳过一点一字节比较一些

作为一项规则,证明平等的唯一捷径证明了身份。在OO代码中,它将显示两个实际上相同对象的对象。文件中最接近的是绑定或NTFS连接意味着两个路径是同一个文件。这种情况很少发生,除非工作的性质比平常更常见,否则检查不会是净收益。

因此,我们在寻找不匹配方面做得很短缺。什么都没有增加我们的通行证,但使我们的失败更快:

  1. 大小不同,不是逐字节相等。 Simples!
  2. 如果您要多次检查同一个文件,请将其哈希并记录哈希值。不同的哈希,保证不相等。需要进行一对一比较的文件减少量很大。
  3. 许多文件格式可能有一些共同点。特别是许多格式的第一个字节往往是“魔术数字”,标题等。要么跳过它们,要么跳过然后再检查最后一个(如果它们有可能不同但它很低)。
  4. 然后就是尽可能快地进行实际比较。一次将4个八位字节的批量加载到整数中并进行整数比较通常会比每个八位字节的八位字节快。

    线程可以提供帮助。一种方法是将文件的实际比较分成多个操作,但如果可能的话,通过在不同的线程中进行完全不同的比较,可以找到更大的增益。我需要更多地了解你正在做些什么来提出建议,但最重要的是要确保测试的输出是线程安全的。

    如果您有多个线程检查相同的文件,请让它们相互远离。例如。如果你有四个线程,你可以将文件分成四个,或者你可以有一个取字节0,4,8,而另一个取字节1,5,9等(或4个八位字节组0,4,8等)。 。后者比前者更容易出现false sharing问题,所以不要这样做。

    编辑:

    它还取决于您对文件的处理方式。你说你需要100%的确定性,所以这一点并不适用于你,但是如果假阳性的成本浪费资源,时间或内存而不是实际的失败,那么值得为更普遍的问题补充一点。 ,然后通过模糊的快捷方式减少它可能是一个净赢,可能值得分析,看看是否是这种情况。

    如果你使用哈希加速事情(它至少可以更快找到一些明确的错误匹配),那么Bob Jenkins' Spooky Hash是一个不错的选择;它不是加密安全的,但如果这不是你的目的,它会非常快速地创建128位散列(比加密散列快得多,甚至比许多GetHashCode()实现的方法快得多)非常擅长没有意外碰撞(这种故意碰撞加密哈希避免是另一回事)。我为.Net and put it on nuget实现了它,因为当我发现自己想要使用它时,没有其他人拥有它。

答案 1 :(得分:2)

串行比较

测试文件大小:118 MB
持续时间: 579 ms
等于?真

    static bool Compare(string filePath1, string filePath2)
    {
        using (FileStream file = File.OpenRead(filePath1))
        {
            using (FileStream file2 = File.OpenRead(filePath2))
            {
                if (file.Length != file2.Length)
                {
                    return false;
                }

                int count;
                const int size = 0x1000000;

                var buffer = new byte[size];
                var buffer2 = new byte[size];

                while ((count = file.Read(buffer, 0, buffer.Length)) > 0)
                {
                    file2.Read(buffer2, 0, buffer2.Length);

                    for (int i = 0; i < count; i++)
                    {
                        if (buffer[i] != buffer2[i])
                        {
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    }


并行比较

测试文件大小:118 MB
持续时间: 340 ms
等于?真

    static bool Compare2(string filePath1, string filePath2)
    {
        bool success = true;

        var info = new FileInfo(filePath1);
        var info2 = new FileInfo(filePath2);

        if (info.Length != info2.Length)
        {
            return false;
        }

        long fileLength = info.Length;
        const int size = 0x1000000;

        Parallel.For(0, fileLength / size, x =>
        {
            var start = (int)x * size;

            if (start >= fileLength)
            {
                return;
            }

            using (FileStream file = File.OpenRead(filePath1))
            {
                using (FileStream file2 = File.OpenRead(filePath2))
                {
                    var buffer = new byte[size];
                    var buffer2 = new byte[size];

                    file.Position = start;
                    file2.Position = start;

                    int count = file.Read(buffer, 0, size);
                    file2.Read(buffer2, 0, size);

                    for (int i = 0; i < count; i++)
                    {
                        if (buffer[i] != buffer2[i])
                        {
                            success = false;
                            return;
                        }
                    }
                }
            }
        });

        return success;
    }


MD5比较

测试文件大小:118 MB
持续时间: 702 ms
等于?真

    static bool Compare3(string filePath1, string filePath2)
    {
        byte[] hash1 = GenerateHash(filePath1);
        byte[] hash2 = GenerateHash(filePath2);

        if (hash1.Length != hash2.Length)
        {
            return false;
        }

        for (int i = 0; i < hash1.Length; i++)
        {
            if (hash1[i] != hash2[i])
            {
                return false;
            }
        }

        return true;
    }

    static byte[] GenerateHash(string filePath)
    {
        MD5 crypto = MD5.Create();

        using (FileStream stream = File.OpenRead(filePath))
        {
            return crypto.ComputeHash(stream);
        }
    }

tl; dr并行比较字节段以确定两个文件是否相等。

答案 2 :(得分:1)

为什么不两者?

与第一遍的哈希比较,然后返回冲突并执行逐字节比较。这允许最大速度,保证100%匹配置信度。

答案 3 :(得分:1)

如果你想要完美的比较(文件仍然需要逐字节读取以进行任何散列),就没有避免进行逐字节比较,因此问题在于你如何阅读和比较数据

因此,您需要解决两件事:

  • 并发 - 确保在检查数据的同时阅读数据。
  • 缓冲区大小 - 一次读取1个字节的文件会很慢,请确保将其读入一个合适大小的缓冲区(大约8MB应该可以很好地处理非常大的文件)

目标是确保您可以像硬盘读取数据一样快地进行比较,并确保总是无延迟地读取数据。如果您正在尽可能快地从驱动器读取数据,那么就可以尽可能快地执行此操作,因为硬盘读取速度成为瓶颈。

答案 4 :(得分:1)

最终,哈希将逐字节地读取文件...所以如果您正在寻找准确的比较,那么您也可以进行比较。你能否提供一些关于你想要完成的事情的更多背景知识? “大”文件有多大?你经常需要比较它们吗?

答案 5 :(得分:1)

如果您有大量文件并且您正在尝试识别重复项,我会尝试按费用顺序细分工作。 我可以尝试以下内容:

1)按大小分组文件。不同大小的文件显然不能相同。检索此信息非常便宜。如果每个组只包含1个文件,则表示您已完成,没有欺骗,否则请继续执行步骤2.

2)在每个大小组内生成文件的前n个字节的散列。确定可能检测到差异的合理n。许多文件具有相同的标题,因此您不必确保n大于标题长度。按哈希分组,如果每个组包含1个文件,则表示已完成(此组中没有欺骗),否则请继续执行步骤3.

3)此时你可能不得不做更多昂贵的工作,比如生成整个文件的哈希值,或者进行逐字节比较。根据文件的数量和文件内容的性质,您可以尝试不同的方法。希望以前的分组能够缩小可能的重复数,以便您实际需要完全扫描的文件数量非常少。

答案 6 :(得分:0)

要计算哈希值,需要读取整个文件。

如何将两个文件一起打开,并将它们按块进行比较?

伪代码:

open file A
open file B
while file A has more data
{
    if next chunk of A != next chunk of B return false
}
return true

这样您就不会加载太多,如果您之前发现不匹配,则不会读取整个文件。您应该设置一个可以改变块大小的测试,以确定最佳性能的正确大小。

相关问题