【问题标题】:Find duplicates in large file在大文件中查找重复项
【发布时间】:2012-03-02 05:11:30
【问题描述】:

我的文件非常大,大约有 1500 万个条目。 文件中的每一行都包含一个字符串(称为键)。

我需要使用 java.xml 查找文件中的重复条目。 我尝试使用哈希图并检测重复条目。 显然这种方法给我一个“java.lang.OutOfMemoryError: Java heap space”错误。

我该如何解决这个问题?

我想我可以增加堆空间并尝试一下,但我想知道是否有更有效的解决方案而无需调整堆空间。

【问题讨论】:

  • Offtopic:你最初是如何获得 1500 万个条目的?
  • 最好的工作方式是不要重复。不需要删除重复项。
  • @Martijn Courteaux:你不知道这是什么数据。例如,如果您有一本书,并且想知道书中使用了哪些单词,那么首先没有办法避免像 the 这样的重复。
  • @Martijn Courteaux - 你在哪里工作?您是否总是要求系统的所有输入都采用使您的生活更轻松的格式?我想在那里工作!
  • @Martijn Courteaux - 啊,所以你还年轻和乐观:) 问题不只是“菜鸟”。现实世界是混乱的。我们的部分工作是克服混乱并产生有用的东西。想象一下,如果 Google 只索引具有正确拼写的英语单词和完美语法的网页。或者,如果福特制造的汽车只能在阳光明媚的天气下在全新的道路上行驶。

标签: algorithm data-structures


【解决方案1】:

关键是您的数据不适合内存。您可以为此使用external merge sort

将您的文件分成多个适合内存的较小块。对每个块进行排序,消除重复项(现在是相邻元素)。

合并块并在合并时再次消除重复项。由于您将在这里进行 n-nway 合并,因此您可以将每个块中的下 k 个元素保留在内存中,一旦块的项目耗尽(它们已经被合并),就会从磁盘中获取更多。

【讨论】:

  • 不要使用固定大小的批次,而是继续阅读,直到您看到足够多的独特行以将您的字典增加到一定容量,然后将其写为已排序的批次以进行外部合并。在stackoverflow.com/questions/32535222/… 上查看我的回答。只需要重复项意味着您可以优化合并阶段,只要您将批次数量合并到您可以在一个足够宽的合并中看到 所有 已排序批次的程度。
【解决方案2】:

您可能无法一次加载整个文件,但您可以将哈希和行号存储在 HashSet 中没问题。

伪代码...

for line in file
    entries.put(line.hashCode, line-number)
for entry in entries
    if entry.lineNumbers > 1
         fetch each line by line number and compare

【讨论】:

  • 或存储行的 MD5 或 SHA1 哈希字典,并假设不同行不会发生冲突。当该散列的计数从 1 变为 2 时,打印您刚刚散列的输入行。输出将是复制的每一行的一份副本。如果您确实需要存储某些内容的行号,请改为存储字节偏移量。文本文件不能通过行号随机访问,因为它们是可变长度的并且没有映射。
【解决方案3】:

我可以想象解决这个问题的一种方法是首先使用external sorting algorithm 对文件进行排序(搜索external sort java 会产生大量带有代码的结果)。然后你可以逐行迭代文件,现在重复显然会直接相互跟随,所以你只需要在迭代时记住前一行。

【讨论】:

  • 如果重复项不在相邻行中怎么办?
  • @hellodear:这里排序的重点是保证重复相邻行。
【解决方案4】:

如果由于内存不足而无法构建完整列表,则可以尝试循环执行。 IE。创建一个 hashmap 但只存储一小部分项目(例如,以 A 开头的项目)。然后你收集重复的,然后继续'B'等。

当然,您可以选择任何类型的“分组”(即前 3 个字符、前 6 个字符等)。

这只需要(很多)更多的迭代。

【讨论】:

    【解决方案5】:

    我认为您不需要对数据进行排序以消除重复项。只需使用受快速排序启发的方法即可。

    1. 从数据中选择 k 个枢轴(除非您的数据非常古怪,否则这应该很简单)
    2. 使用这 k 个主元将数据分成 k+1 个小文件
    3. 如果这些块中的任何一个太大而无法放入内存中,则仅针对该块重复该过程
    4. 一旦您有了可管理大小的块,只需应用您最喜欢的方法(散列?)来查找重复项

    注意 k 可以等于 1。

    【讨论】:

    • 所以第 1 步和第 2 步实际上是:挑选 k 个元素并对其进行排序。通读文件:对于每一行,二进制搜索您的枢轴数组,并将该行写入存储桶i,其中pivot[i-1] < line < pivot[i]。如果您的字符串具有相当均匀的第一个字符分布,则使用第一个或两个字符作为基数将输入分散到存储桶中会容易得多,而不是搜索枢轴列表。
    【解决方案6】:

    我不确定您是否会考虑在 java 之外执行此操作,但如果是这样,这在 shell 中非常简单:

    cat file | sort | uniq
    

    【讨论】:

    • @augurar:OP 询问查找哪些条目是重复的,而不是唯一的输出。 sort file | uniq --repeated(又名uniq -d)。 Michael:永远不要写cat file | something,与something < file(或something file,如果结果相同)相比,这很愚蠢,而且浪费CPU时间和内存带宽。
    • @PeterCordes 真的,我的评论与这个答案有关。
    【解决方案7】:

    如果您愿意接受一定数量的统计错误,您可以尝试Bloom filter。 Guava provides 一个,但它现在有一个相当大的错误,应该会在下周发布 11.0.2 时修复。

    【讨论】:

    • 这也是我的答案。可以在第二阶段消除误报(候选列表的大小会小得多)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-14
    • 1970-01-01
    相关资源
    最近更新 更多