【发布时间】:2013-05-17 14:26:29
【问题描述】:
我正在处理一个处理大量推文的项目;目标是在我处理重复项时删除它们。我有推文 ID,它以 "166471306949304320" 格式的字符串形式出现
我一直在为此使用HashSet<String>,它可以正常工作一段时间。但是当我达到大约 1000 万个项目时,我彻底陷入了困境,并最终得到了一个 GC 错误,大概来自重新散列。我尝试使用
tweetids = new HashSet<String>(220000,0.80F);
这让它走得更远一点,但仍然非常缓慢(大约 1000 万,它需要 3 倍的处理时间)。我该如何优化呢?鉴于我大概知道最后应该有多少项目(在这种情况下,大约 20-22 百万)我应该创建一个只重新散列两次或三次的 HashSet,或者这样的开销设置招致太多的时间惩罚?如果我不使用字符串,或者如果我定义不同的 HashCode 函数(在这种情况下是字符串的特定实例,我不知道该怎么做),事情会更好吗?这部分实现代码如下。
tweetids = new HashSet<String>(220000,0.80F); // in constructor
duplicates = 0;
...
// In loop: For(each tweet)
String twid = (String) tweet_twitter_data.get("id");
// Check that we have not processed this tweet already
if (!(tweetids.add(twid))){
duplicates++;
continue;
}
解决方案
感谢您的建议,我解决了。问题在于哈希表示所需的内存量;首先,HashSet<String> 非常庞大且不需要,因为String.hashCode() 对于这种规模来说太高了。接下来我尝试了一个 Trie,但它在超过 100 万个条目时崩溃了;重新分配数组是有问题的。我使用HashSet<Long> 来获得更好的效果,几乎成功了,但是速度下降了,最终在处理的最后一站(大约 1900 万)崩溃了。解决方案是脱离标准库并使用Trove。它完成 2200 万条记录比根本不检查重复要快几分钟。最终实现很简单,看起来像这样:
import gnu.trove.set.hash.TLongHashSet;
...
TLongHashSet tweetids; // class variable
...
tweetids = new TLongHashSet(23000000,0.80F); // in constructor
...
// inside for(each record)
String twid = (String) tweet_twitter_data.get("id");
if (!(tweetids.add(Long.parseLong(twid)))) {
duplicates++;
continue;
}
【问题讨论】:
-
如何将 ID 视为数字,找到一个好的基值,然后处理差异?然后您可以使用
HashSet<Long>,它的性能应该优于字符串;您还可以使用 Trove 库来处理原语。 -
你不能简单地增加你的堆大小吗?
-
如果知道集合最终会包含2200万个项目,为什么不从一开始就创建一个容量为22_000_000 / 0.75的HashSet呢?这样可以防止任何重新散列。
-
@JBNizet 你的意思是 22_000_000 / 1.0?
-
至于使用
java -Xms2gb之类的东西来增加堆大小,我的理解是,这将是针对 GC 错误的创可贴,但对显着降低速度没有帮助。
标签: java optimization hashset duplicate-removal