Algorithm 在大文件中查找重复项

Algorithm 在大文件中查找重复项,algorithm,data-structures,Algorithm,Data Structures,我有一个很大的文件,大约有1500万条条目。 文件中的每一行都包含一个字符串(称为key) 我需要使用java在文件中找到重复的条目。 我尝试使用hashmap并检测重复条目。 显然,这种方法给我带来了一个“java.lang.OutOfMemoryError:java堆空间”错误 我怎样才能解决这个问题 我想我可以增加堆空间并尝试一下,但我想知道是否有更有效的解决方案,而不必调整堆空间。关键是数据无法放入内存。您可以为此使用: 将文件划分为多个适合内存的较小块。对每个块进行排序,消除重复项(现

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

我需要使用java在文件中找到重复的条目。 我尝试使用hashmap并检测重复条目。 显然,这种方法给我带来了一个“java.lang.OutOfMemoryError:java堆空间”错误

我怎样才能解决这个问题


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

关键是数据无法放入内存。您可以为此使用:

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


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

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

伪代码

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

我可以想象解决这个问题的一种方法是首先使用一个函数对文件进行排序(搜索
externalsortjava
会产生大量带有代码的结果)。然后,您可以逐行迭代文件,副本现在显然会紧随其后,因此您只需在迭代时记住前一行。

如果由于内存不足而无法建立完整的列表,您可以尝试循环执行。即,创建一个hashmap,但只存储一小部分项(例如,以a开头的项)。然后收集副本,然后继续使用“B”等

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


它只需要(许多)更多的迭代。

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

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

  • 注意到k可以等于1。

    < p>我不确定你是否考虑在java之外做这个,但是如果是这样,shell中的这个非常简单:

    cat file | sort | uniq
    

    如果你愿意接受一定数量的统计错误,你可以尝试一个。番石榴一号,但它现在有一个相当大的错误,应该在下周11.0.2版中修复。

    Offtopic:你是如何获得1500万条参赛作品的?最好的工作方式应该是不要有重复的作品。不需要删除重复项。@Martijn Courtaux:您不知道这是什么类型的数据。例如,如果你有一本书,并且想知道书中使用了哪些单词,那么就没有办法避免重复,比如说
    the
    。@Martijn Courtaux-你在哪里工作?您是否总是要求系统的所有输入都采用使您的生活更轻松的格式?我想在那里工作@Martijn Courtaux-啊,你还年轻乐观:)但问题不只是“noobs”。现实世界是混乱的。我们工作的一部分是克服混乱,生产有用的东西。想象一下,如果谷歌只为拼写正确的英语单词和语法完美的网页编制索引。或者,如果福特制造的汽车只能在晴朗的天气下在全新的道路上行驶。如果复制品不在相邻的生产线上呢?@hellodear:这里的排序点是保证复制品在相邻的生产线上。而不是固定大小的批次,继续阅读,直到你看到足够多的独特的行来增加你的字典到一定的容量,然后把它作为一个排序的批进行外部合并。请看我的答案。只需要重复项意味着您可以对合并阶段进行大量优化,只要将批次数合并到一个足够宽的合并范围内,您就可以看到所有已排序的批次。@augurar:OP询问的是查找哪些条目是重复的,而不是未经限定的输出<代码>排序文件| uniq--重复(也称为
    uniq-d
    )。迈克尔:永远不要写
    cat文件|某个东西
    ,与
    某个东西
    (或
    某个文件
    ,如果结果相同的话)相比,这是愚蠢的,是CPU时间和内存带宽的浪费。@PeterCordes-True,我的评论与这个答案有关。或者存储一个MD5或SHA1行哈希的字典,假设不相同的线不会发生碰撞。当散列的计数从1变为2时,打印刚刚散列的输入行。输出将是复制的每一行的一个副本。如果您确实需要存储某些内容的行号,请改为存储字节偏移量。文本文件不能按行号随机访问,因为它们是可变长度的,并且没有映射。所以步骤1和2实际上是:选择k个元素并对它们进行排序。通读文件:对于每一行,二进制搜索pivot数组,并将该行写入bucket
    i
    ,其中
    pivot[i-1]
    。如果字符串的第一个字符的分布相当均匀,那么使用第一个或两个字符作为基数将输入分散到存储桶中要容易得多,而不是搜索数据轴列表。这也是我的答案。假阳性可以在第二阶段消除(候选列表的大小会小得多)