我希望这个问题对这个论坛来说不是太基本的,但我们会看到的。我想知道如何重构一些代码以获得更好的性能,这是运行了很多次。

假设我正在使用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框架而使用其他框架呢?

更新:我已经对几个答案做了测试。见下文。


当前回答

另一种方法是创建一个可变整数:

class MutableInt {
  int value = 0;
  public void inc () { ++value; }
  public int get () { return value; }
}
...
Map<String,MutableInt> map = new HashMap<String,MutableInt> ();
MutableInt value = map.get (key);
if (value == null) {
  value = new MutableInt ();
  map.put (key, value);
} else {
  value.inc ();
}

当然,这意味着创建一个额外的对象,但与创建一个Integer(即使是Integer. valueof)相比,开销不应该那么多。

其他回答

很简单,只需使用Map.java中的内置函数,如下所示

map.put(key, map.getOrDefault(key, 0) + 1);

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);

在java 8中,简单易行的方法如下:

final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.computeIfAbsent("foo", key -> new AtomicLong(0)).incrementAndGet();

希望我正确理解了你的问题,我从Python来到Java,所以我可以同情你的挣扎。

如果你有

map.put(key, 1)

你会这么做

map.put(key, map.get(key) + 1)

希望这能有所帮助!

2016年一点研究:https://github.com/leventov/java-word-count,基准源代码

每种方法的最佳效果(越小越好):

                 time, ms
kolobokeCompile  18.8
koloboke         19.8
trove            20.8
fastutil         22.7
mutableInt       24.3
atomicInteger    25.3
eclipse          26.9
hashMap          28.0
hppc             33.6
hppcRt           36.5

时间、空间的结果: