当单词超过2亿时,如何使用Java删除重复的单词?

时间:2012-09-19 18:57:23

标签: java duplicate-removal

我有一个文件(大小= ~1.9 GB),其中包含~220,000,000(~2亿)单词/字符串。它们有重复,每100个字几乎有1个重复字。

在我的第二个程序中,我想读取该文件。我成功地使用BufferedReader按行读取文件。

现在要删除重复项,我们可以使用Set(和它的实现),但是Set有问题,如下面3种不同场景所述:

  1. 使用默认的JVM大小,Set最多可包含0.7-0.8百万字,然后是OutOfMemoryError。
  2. 使用512M JVM大小,Set最多可包含5-6百万字,然后是OOM错误。
  3. 使用1024M JVM大小,Set最多可包含12-13百万字,然后是OOM错误。在将1000万条记录添加到Set中之后,操作变得极其缓慢。例如,添加下一个~4000条记录,需要60秒。
  4. 我有限制,我无法进一步增加JVM大小,我想从文件中删除重复的单词。

    如果您对从这样一个巨大的文件中使用Java删除重复单词的任何其他方法/方法有任何了解,请告诉我。非常感谢:)

    添加问题信息:我的单词基本上是字母数字,它们是我们系统中唯一的ID。因此,它们不是简单的英语单词。

13 个答案:

答案 0 :(得分:14)

使用merge sort并在第二遍中删除重复项。您甚至可以在合并时删除重复项(只需将最新的单词添加到RAM中的输出中,并将候选项与其进行比较)。

答案 1 :(得分:11)

根据单词的第一个字母将巨大的文件分成26个较小的文件。如果任何字母文件仍然太大,请使用第二个字母除以该字母文件。

使用Set分别处理每个字母文件以删除重复项。

答案 2 :(得分:7)

您可以使用trie数据结构一次完成这项工作。它具有推荐它用于此类问题的优点。查找和插入很快。它的表现相对空间有效。您可以在RAM中表示所有单词。

答案 3 :(得分:5)

如果对项目进行排序,重复项将很容易被检测和删除,因为重复项将聚集在一起。

这里有代码可以用来合并大文件: http://www.codeodor.com/index.cfm/2007/5/10/Sorting-really-BIG-files/1194

答案 4 :(得分:4)

对于大文件,我尽量不将数据读入内存,而是操作内存映射文件,并根据需要将操作系统页面输入/输出内存。如果您的set结构包含此内存映射文件的偏移而不是实际的字符串,那么它将消耗更少的内存。

查看这篇文章:

http://javarevisited.blogspot.com/2012/01/memorymapped-file-and-io-in-java.html

答案 5 :(得分:4)

问题:这些真的是单词,还是别的东西 - 短语,部分号码等?

对于普通口语中的单词,可以预期在前几千个单词之后你会找到大部分独特的单词,所以你真正需要做的就是读一个单词,检查字典,如果发现跳过它,如果没有找到,请将其添加到字典中并将其写出来。

在这种情况下,你的词典只有几千字大。而且您不需要保留源文件,因为您在找到它们后立即写出了这些独特的单词(或者您可以在完成后简单地转储字典)。

答案 6 :(得分:4)

如果您可以在数据库的临时表中插入单词(使用批量插入),那么它将是对该表的选择。

答案 7 :(得分:3)

解决此类问题的一种经典方法是Bloom filter。基本上你会多次散列你的单词,并且对于每个散列结果集,在位向量中有一些位。如果你正在检查一个单词并且它的散列中的所有位都是在向量中设置的(你可以通过增加向量中的散列/位的数量来设置这个概率任意低),之前看到它并且它是重复的。

这就是早期拼写检查工作的方式。他们知道字典中是否有单词,但是他们无法告诉你正确的拼写是什么,因为它只会告诉你当前的单词是否被看到。

有许多开源实现,包括java-bloomfilter

答案 8 :(得分:1)

我在Java中使用与其他语言相同的方式处理此问题:编写重复数据删除过滤器并根据需要经常管道。

这就是我的意思(伪代码):

  • 输入参数:OffsetSize
  • 分配大小为Size(= Set的可搜索结构,但不一定是一个)
  • 从stdin中读取Offset(或遇到EOF)元素并将它们复制到stdout
  • 从stdin(或EOF)中读取Size元素,将它们存储在Set中。如果重复,则删除,否则写入stdout。
  • 从stdin读取元素直到EOF,如果它们在Set然后删除,否则写入stdout

现在根据需要管道尽可能多的实例(如果存储没有问题,可能只有你有核心的数量),增加Offset s和理智Size。这允许您使用更多内核,因为我怀疑该进程是CPU绑定的。如果你赶时间的话,你甚至可以使用netcat并在更多的机器上传播处理。

答案 9 :(得分:1)

为了不必担心实现,您应该使用数据库系统,简单的旧关系SQL或No-SQL解决方案。我很确定你可以使用例如Berkeley DB java版再做(伪代码)

for(word : stream) {
  if(!DB.exists(word)) {
     DB.put(word)
     outstream.add(word)
  }
}

问题本质上很简单,你需要将内容存储在磁盘上因为内存不足,然后使用排序O(N log N)(不必要)或散列O(N)来查找唯一的单词。 / p>

如果您想要一个非常有效的解决方案,但不能保证这样做,请使用LRU类型的哈希表。根据经验Zpif's law你应该没问题。

对一些聪明人的跟进问题,如果我有64位机器并将堆大小设置为12GB,应该不是虚拟内存处理问题(尽管不是以最佳方式)或者是java不是这样设计的吗?

答案 10 :(得分:1)

即使在英语中,自然语言的单词数量也很多,但估计上限只有大约80000个单词。基于此,您可以使用HashSet并添加所有单词(可能全部小写以避免出现问题):

Set<String> words = new HashSet<String>();
while (read-next-word) {
    words.add(word.toLowerCase());
}

如果它们是真实的话,这不会导致记忆问题,也会很快!

答案 11 :(得分:0)

在这种情况下,Quicksort比Mergesort更好,因为它需要更少的内存。 This thread对原因有很好的解释。

答案 12 :(得分:0)

大多数高性能解决方案都来自省略不必要的东西。你只看重复,所以不要存储单词本身,存储哈希值。但是等等,你对哈希也不感兴趣,只要他们已经看过了 - 不要存储它们。将哈希视为非常大的数字,并使用bitset查看您是否已经看到此数字。

所以你的问题归结为非常大的稀疏填充位图 - 大小取决于散列宽度。如果您的哈希值高达32位,则可以使用riak位图。

...开始思考128+位哈希的真正大位图%)(我会回来的)