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

假设我正在使用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)相比,开销不应该那么多。

其他回答

谷歌集合HashMultiset: -使用起来相当优雅 —但会消耗CPU和内存

最好是有这样一个方法:Entry<K,V> getOrPut(K); (美观,成本低)

这样的方法只计算哈希和索引一次, 然后我们可以对元素做我们想做的 (替换或更新值)。

更优雅: -取一个HashSet<Entry> -扩展它,以便get(K)在需要时放置一个新的条目 -入口可以是你自己的对象。 ——> (new MyHashSet()).get(k).increment();

番石榴是你的朋友…

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

E.g.

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

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

map.getAndAdd(word, 112L); 

我将使用Apache Collections Lazy Map(将值初始化为0),并使用Apache Lang中的MutableIntegers作为该映射中的值。

在您的方法中,最大的代价是必须搜索两次地图。在我这里,你只需要做一次。只需要获取值(如果没有,它将被初始化)并增加它。

作为我自己评论的后续:Trove看起来是可行的。如果出于某种原因,您希望坚持使用标准JDK,那么ConcurrentMap和AtomicLong可以使代码稍微好一点,不过使用YMMV。

    final ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
    map.putIfAbsent("foo", new AtomicLong(0));
    map.get("foo").incrementAndGet();

将在foo的map中保留1作为值。实际上,增加线程的友好性是这种方法所推荐的。

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

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