我希望这个问题对这个论坛来说不是太基本的,但我们会看到的。我想知道如何重构一些代码以获得更好的性能,这是运行了很多次。
假设我正在使用Map(可能是HashMap)创建一个词频列表,其中每个键都是一个包含要统计的单词的String,值是一个Integer,该Integer在每次找到单词的标记时递增。
在Perl中,增加这样一个值非常简单:
$map{$word}++;
但在Java中,这要复杂得多。下面是我目前的做法:
int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);
这当然依赖于新Java版本中的自动装箱特性。我想知道您是否可以建议一种更有效的方法来增加这个值。是否有更好的性能理由避开Collections框架而使用其他框架呢?
更新:我已经对几个答案做了测试。见下文。
MutableInt方法的一个变体可能更快,如果有点hack,是使用一个单元素int数组:
Map<String,int[]> map = new HashMap<String,int[]>();
...
int[] value = map.get(key);
if (value == null)
map.put(key, new int[]{1} );
else
++value[0];
如果您可以使用此变体重新运行性能测试,那将非常有趣。这可能是最快的。
编辑:上面的模式对我来说很好,但最终我改变使用Trove的集合来减少我正在创建的一些非常大的地图的内存大小——作为奖励,它也更快。
一个非常好的特性是TObjectIntHashMap类有一个单独的adjustOrPutValue调用,根据该键是否已经有一个值,它将放置一个初始值或增加现有值。这对于增量来说是完美的:
TObjectIntHashMap<String> map = new TObjectIntHashMap<String>();
...
map.adjustOrPutValue(key, 1, 1);
“put”需要“get”(以确保没有重复的密钥)。
所以直接做一个"看跌"
如果之前有一个值,那么做加法:
Map map = new HashMap ();
MutableInt newValue = new MutableInt (1); // default = inc
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
newValue.add(oldValue); // old + inc
}
如果count从0开始,则添加1:(或任何其他值…)
Map map = new HashMap ();
MutableInt newValue = new MutableInt (0); // default
MutableInt oldValue = map.put (key, newValue);
if (oldValue != null) {
newValue.setValue(oldValue + 1); // old + inc
}
注意:这段代码不是线程安全的。使用它来构建然后使用映射,而不是并发地更新它。
优化:在一个循环中,保留旧值成为下一个循环的新值。
Map map = new HashMap ();
final int defaut = 0;
final int inc = 1;
MutableInt oldValue = new MutableInt (default);
while(true) {
MutableInt newValue = oldValue;
oldValue = map.put (key, newValue); // insert or...
if (oldValue != null) {
newValue.setValue(oldValue + inc); // ...update
oldValue.setValue(default); // reuse
} else
oldValue = new MutableInt (default); // renew
}
}
有几种方法:
使用袋子算法,比如谷歌集合中包含的集合。
创建可变容器,你可以在Map中使用:
class My{
String word;
int count;
}
并使用put(“word”,new My(“word”));然后您可以检查它是否存在,并在添加时增加。
避免使用列表来滚动您自己的解决方案,因为如果您使用内循环搜索和排序,您的性能将会非常糟糕。第一个HashMap解决方案实际上相当快,但在谷歌Collections中找到的合适的解决方案可能更好。
使用谷歌集合计数单词,看起来像这样:
HashMultiset s = new HashMultiset();
s.add("word");
s.add("word");
System.out.println(""+s.count("word") );
使用HashMultiset是非常优雅的,因为在计数单词时,袋算法正是您所需要的。