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

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

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


当前回答

内存旋转在这里可能是一个问题,因为对大于或等于128的int进行装箱都会导致对象分配(参见Integer.valueOf(int))。尽管垃圾收集器非常有效地处理存在时间很短的对象,但性能会在一定程度上受到影响。

如果您知道增量的数量将大大超过键的数量(在本例中为=words),请考虑使用int holder。Phax已经为此提供了代码。这里又是一次,有两个变化(holder类是静态的,初始值设置为1):

static class MutableInt {
  int value = 1;
  void inc() { ++value; }
  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();
}

如果需要极致的性能,请寻找直接针对基本值类型定制的Map实现。jrudolph提到了GNU Trove。

顺便说一下,这个主题的一个很好的搜索词是“直方图”。

其他回答

你应该意识到你最初的尝试

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.

番石榴是你的朋友…

...至少在某些情况下是这样。他们有这个很好的AtomicLongMap。特别好,因为你在地图上处理的是长值。

E.g.

AtomicLongMap<String> map = AtomicLongMap.create();
[...]
map.getAndIncrement(word);

也可以在值上增加多于1的值:

map.getAndAdd(word, 112L); 

“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
  }
}

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

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

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

时间、空间的结果: