【问题标题】:What is the fastest way to find if an array of byte arrays contains another byte array?查找字节数组数组是否包含另一个字节数组的最快方法是什么?
【发布时间】:2009-06-09 21:44:20
【问题描述】:

我有一些非常慢的代码。我知道它会是,现在它是。基本上,我正在从一堆目录中读取文件。文件名改变,但数据不变。为了确定我是否已读取该文件,我对它的字节进行哈希处理并将其与已处理文件的哈希列表进行比较。每个目录中大约有 1000 个文件,弄清楚每个目录中的新内容需要花费一分钟左右的时间(然后开始处理)。这是基本代码:

public static class ProgramExtensions
{
    public static byte[] ToSHA256Hash(this FileInfo file)
    {
        using (FileStream fs = new FileStream(file.FullName, FileMode.Open))
        {
            using (SHA256 hasher = new SHA256Managed())
            {
                return hasher.ComputeHash(fs);
            }
        }
    }
    public static string ToHexString(this byte[] p)
    {

        char[] c = new char[p.Length * 2 + 2];

        byte b;

        c[0] = '0'; c[1] = 'x';

        for (int y = 0, x = 2; y < p.Length; ++y, ++x)
        {
            b = ((byte)(p[y] >> 4));

            c[x] = (char)(b > 9 ? b + 0x37 : b + 0x30);

            b = ((byte)(p[y] & 0xF));

            c[++x] = (char)(b > 9 ? b + 0x37 : b + 0x30);
        }

        return new string(c);

    }
}

class Program
{
    static void Main(string[] args)
    {
        var allFiles = new DirectoryInfo("c:\\temp").GetFiles("*.*");

        List<string> readFileHashes = GetReadFileHashes();

        List<FileInfo> filesToRead = new List<FileInfo>();

        foreach (var file in allFiles)
        {
            if (readFileHashes.Contains(file.ToSHA256Hash().ToHexString()))
                filesToRead.Add(file);
        }

        //read new files
    }
}

有什么办法可以加快速度吗?

【问题讨论】:

    标签: c# comparison reference-type bytearray


    【解决方案1】:

    我相信您可以通过简单地首先检查文件大小来存档最显着的性能改进,如果文件大小不匹配,您可以跳过整个文件甚至不打开它。

    除了保存已知哈希列表之外,您还可以保留已知文件大小的列表,并且仅在文件大小匹配时进行内容比较。当文件大小不匹配时,您甚至可以避免查看文件内容。

    根据您的文件通常具有的一般大小,值得进一步改进:

    • 当第一个字节不同时,要么进行二进制比较,要么提前中止(节省读取整个文件,如果您的文件通常很大,这可能是一个非常显着的改进,任何哈希算法都会读取整个文件。检测第一个字节不同可以使您免于阅读文件的其余部分)。如果您的查找文件列表可能包含许多相同大小的文件,那么您可能不得不对多个文件进行二进制比较,请考虑:

    • 散列在每个 1MB 的块中。首先仅根据查找中预先计算的第一个块哈希检查第一个块。如果第一个块相同,则仅比较第二个块,在大多数情况下为不同的文件保存超过第一个块的读取。只有当您的文件很大时,这两个选项才真正值得付出努力。

    我怀疑更改散列算法本身(例如,按照建议首先检查执行 CRC)会产生任何显着差异。您的瓶颈可能是磁盘 IO,而不是 CPU,因此避免磁盘 IO 会给您带来最大的改进。但与以往一样,在性能方面,衡量。

    然后,如果这仍然不够(仅此而已),请尝试异步 IO(请记住,尽管顺序读取通常比随机访问快,因此过多的随机异步读取会损害您的性能)

    【讨论】:

    • 我需要比这更可靠的东西。这些文件是具有固定大小记录的二进制文件。许多文件可以具有相同数量的记录,从而使它们具有相同的大小。
    • 我当然不是检查文件大小,而是在文件大小不匹配时快速丢弃文件,只进一步调查不匹配的文件' t 使用计算成本更高的比较进行匹配。编辑我的答案更清楚
    • 我明白了。我也喜欢提前保释的想法,因为我认为前 20 个字节左右通常是不同的。
    【解决方案2】:
    • 创建文件列表
    • 按文件大小对列表进行排序
    • 从列表中删除具有唯一大小的文件
    • 现在进行散列(首先快速散列也可以提高性能)

    【讨论】:

      【解决方案3】:
      • 为您的 readFileHashes 存储使用具有高效搜索能力(散列或二进制搜索)的数据结构。我认为 HashSet 或 TreeSet 会在这里为您提供更好的服务。

      • 使用适当的校验和(哈希和)函数。 SHA256 是一种加密散列,可能有点矫枉过正。 CRC 的计算成本较低,最初用于捕获无意/随机更改(传输错误),但容易受到设计/旨在隐藏的更改的影响。什么适合您正在扫描的文件之间的差异?

        http://en.wikipedia.org/wiki/List_of_checksum_algorithms#Computational_costs_of_CRCs_vs_Hashes

        通过采样的非常简单的校验和(例如校验和 =(前 10 个字节和后 10 个字节))是否有效?

      【讨论】:

      • 我很惊讶(但很高兴以其他方式显示)哈希算法的效率与磁盘 I/O 的成本相比将产生显着差异。现代 CPU 以典型 HDD 可以读取数据的速度执行 SHA256 应该没有任何问题。 RAID 中的快速 SSDd 可能......但我喜欢你的采样想法,因为它可以让你免于执行 IO
      • 一个很好的观点 - 我只是讨厌看到使用昂贵的算法,可能是不必要的,只是因为更新是“更好”。因此,当 iPhone/LinuxOnANasChip/NetBook 程序员将来在 StackOverflow 中寻找答案时,我希望他们考虑在给定的上下文中应该使用哪种算法。 :-)
      • 是的,很容易忘记算法有多复杂,例如SHA256 在现代环境中非常易于使用
      【解决方案4】:

      我会先做一个快速的 CRC 哈希检查,因为它比较便宜。 如果 CRC 不匹配,则继续进行更“可靠”的哈希测试,例如 SHA

      【讨论】:

        【解决方案5】:

        您对问题的描述仍然不够清楚。

        最大的问题是你正在做一堆散列。这肯定会很慢。

        您可能想尝试搜索修改时间,如果文件名更改,该时间不会更改:

        http://msdn.microsoft.com/en-us/library/ms724320(VS.85,loband).aspx

        或者您可能想要监视文件夹是否有任何新文件更改:

        http://www.codeguru.com/forum/showthread.php?t=436716

        【讨论】:

          【解决方案6】:

          首先按文件大小对文件进行分组 - 这将为您留下较小的文件组。现在它取决于组大小和文件大小。您可以开始并行读取所有文件,直到找到不同之处。如果存在差异,请将组拆分为在当前位置具有相同值的较小组。如果您知道文件有何不同,则可以使用此信息 - 从最后开始阅读,如果更大的集群发生变化,或者您对文件的了解如何,请不要逐字节读取和比较。如果您必须并行读取许多文件导致随机磁盘访问,此解决方案可能会引入 I/O 性能问题。

          您还可以计算每个组中所有文件的哈希值并进行比较。您不必一次处理整个文件 - 只需计算几个(可能是 4kiB 集群或任何适合您的文件大小)字节的哈希值并检查是否存在差异。如果不是,请计算接下来几个字节的哈希值。这将使您能够处理每个文件的较大块,而无需为内存中的一组中的每个文件保留一个这样的大块。

          所以这完全是关于时间内存(磁盘 I/O 内存)的权衡。您必须在将组中的所有文件读入内存并逐字节比较它们(高内存要求,快速顺序访问,但可能读取大量数据)和逐字节读取文件并仅比较最后一个字节之间找到自己的方法读取(内存要求低,随机访问速度慢,只读取所需的数据)。此外,如果组非常大,逐字节比较文件会变得更慢 - 比较 n 个文件中的一个字节是 O(n) 操作 - 首先计算哈希值然后只比较哈希可能会变得更有效价值观。

          【讨论】:

            【解决方案7】:

            更新:绝对不要只检查文件大小。如果您的操作系统版本允许使用 FileInfo.LastWriteTime

            我已经为内部项目编译器/打包器实现了类似的功能。我们有超过 8k 个文件,因此我们将最后修改的日期和哈希数据存储到 sql 数据库中。然后在随后的运行中,我们首先查询任何特定文件的修改日期,然后才查询哈希数据……这样我们只计算那些看起来被修改的文件的新哈希数据……

            .net 有一种方法可以在 FileInfo 类中检查最后修改日期。我建议你检查一下。 编辑:这里是链接LastWriteTime

            我们的打包程序需要大约 20 秒来找出哪些文件已被修改。

            【讨论】:

            • 这个想法是在 not 大小相等时丢弃,在大小相等时不进行进一步检查而不进行确认(没有人建议这个,在 Vinko 的回答中,我理解它是候选文件和比较文件共享同一个列表,所以唯一的长度意味着唯一的文件,我的回答可能不够清楚)。
            • 是的,我从答案中得到了这一点,但即使在那之后还有更多的检查,检查文件大小仍然没有意义。我不是要在这里破坏任何人...只是说唯一需要检查的是修改日期,然后如果您真的想 100% 确定,请检查哈希。
            • 经常检查 LastWriteTime 可以减少误报。但请注意,在某些(极少数)情况下,LastWriteTime 可以更改(例如,将文件从 ntfs 复制到 fat 或反之亦然,ntfs 以 utc 存储,本地时间为 fat)而不更改内容。或者程序可能决定不更新上次访问时间,或者更新时间而不更改内容。因此,仅在没有进一步检查的情况下丢弃不同的访问时间会使您错过重复文件。这有多大可能以及有多糟糕取决于您的环境。检查文件大小是可靠的,所以我毕竟是有道理的。
            猜你喜欢
            • 1970-01-01
            • 2016-05-02
            • 1970-01-01
            • 1970-01-01
            • 2019-08-13
            • 1970-01-01
            • 2014-07-12
            • 1970-01-01
            • 2014-02-15
            相关资源
            最近更新 更多