我希望这个问题对这个论坛来说不是太基本的,但我们会看到的。我想知道如何重构一些代码以获得更好的性能,这是运行了很多次。
假设我正在使用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框架而使用其他框架呢?
更新:我已经对几个答案做了测试。见下文。
你应该意识到你最初的尝试
int count = map.containsKey(word) ? map.get(word) : 0;
包含映射上两个可能代价高昂的操作,即containsKey和get。前者执行的操作可能与后者非常相似,因此您要做两次相同的工作!
如果查看Map的API,当Map不包含所请求的元素时,get操作通常返回null。
注意,这将得到一个像
map.put( key, map.get(key) + 1 );
dangerous, since it might yield NullPointerExceptions. You should check for a null first.
Also note, and this is very important, that HashMaps can contain nulls by definition. So not every returned null says "there is no such element". In this respect, containsKey behaves differently from get in actually telling you whether there is such an element. Refer to the API for details.
For your case, however, you might not want to distinguish between a stored null and "noSuchElement". If you don't want to permit nulls you might prefer a Hashtable. Using a wrapper library as was already proposed in other answers might be a better solution to manual treatment, depending on the complexity of your application.
To complete the answer (and I forgot to put that in at first, thanks to the edit function!), the best way of doing it natively, is to get into a final variable, check for null and put it back in with a 1. The variable should be final because it's immutable anyway. The compiler might not need this hint, but its clearer that way.
final HashMap map = generateRandomHashMap();
final Object key = fetchSomeKey();
final Integer i = map.get(key);
if (i != null) {
map.put(i + 1);
} else {
// do something
}
If you do not want to rely on autoboxing, you should say something like map.put(new Integer(1 + i.getValue())); instead.
现在在Java 8中使用Map::merge有一个更短的方法。
myMap.merge(key, 1, Integer::sum)
or
myMap.merge(key, 1L, Long::sum)
分别为长。
它的作用:
如果key不存在,则将1作为值
否则,sum 1等于链接到key的值
更多信息请点击这里。
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);