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

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

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


当前回答

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

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

其他回答

Map<String, Integer> map = new HashMap<>();
String key = "a random key";
int count = map.getOrDefault(key, 0); // ensure count will be one of 0,1,2,3,...
map.put(key, count + 1);

这就是用简单代码增加值的方法。

好处:

不需要添加一个新类或使用可变int的另一个概念 不依赖于任何库 容易理解到底发生了什么(没有太多抽象)

缺点:

将在哈希映射中搜索get()和put()两次。所以它不是性能最好的代码。

从理论上讲,一旦调用get(),您就已经知道在哪里放置(),因此不需要再次搜索。但是在哈希映射中搜索通常只需要很短的时间你可以忽略这个性能问题。

但如果你对这个问题非常认真,你是一个完美主义者,另一种方法是使用合并方法,这(可能)比前面的代码片段更有效,因为你将(理论上)只搜索一次地图:(虽然这段代码乍一看不明显,但它是简短的和性能)

map.merge(key, 1, (a,b) -> a+b);

建议:在大多数情况下,你应该更关心代码的可读性,而不是性能的提高。如果第一个代码片段更容易理解,那么就使用它。但如果你能很好地理解第二个,那么你也可以去做!

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

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

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

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

番石榴是你的朋友…

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

E.g.

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

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

map.getAndAdd(word, 112L); 

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

如果你有

map.put(key, 1)

你会这么做

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

希望这能有所帮助!