用Java增長Map值的最有效方法

我但願這個問題對於本論壇來講不是太基本了,可是咱們會看到的。 我想知道如何重構一些代碼以得到更好的性能,而這些性能已經運行了不少次。 html

假設我正在使用地圖(多是HashMap)建立一個單詞頻率列表,其中每一個鍵是一個帶有要計算單詞的String,而且值是一個Integer,每次找到該單詞的標記時都會增長。 java

在Perl中,增長這樣的值很是容易: git

$map{$word}++;

可是在Java中,它要複雜得多。 這是我目前的操做方式: github

int count = map.containsKey(word) ? map.get(word) : 0;
map.put(word, count + 1);

固然,哪一個依賴於較新的Java版本中的自動裝箱功能。 我想知道您是否能夠建議一種更有效的遞增此值的方法。 避開Collections框架並改用其餘東西,甚至有良好的性能緣由嗎? api

更新:我已經測試了幾個答案。 見下文。 數組


#1樓

一些測試結果

對於這個問題,我已經獲得了不少很好的答案-謝謝你們-所以,我決定進行一些測試,找出哪一種方法實際上最快。 我測試的五種方法是: 框架

  • 我在問題中介紹的「 ContainsKey」方法
  • Aleksandar Dimitrov建議的「 TestForNull」方法
  • Hank Gay建議的「 AtomicLong」方法
  • jrudolph建議的「激勵」方法
  • phax.myopenid.com建議的「 MutableInt」方法

方法

這是我作的... eclipse

  1. 建立了五個相同的類,除了如下所示的差別。 每一個班級都必須執行我所介紹的場景的典型操做:打開一個10MB的文件並讀入它,而後對文件中全部單詞標記的頻率進行計數。 因爲平均只須要3秒鐘,所以我讓它執行了10次頻率計數(而不是I / O)。
  2. 定時10次迭代的循環而不是I / O操做的時間,並基本上使用Java Cookbook中的Ian Darwin的方法記錄所花費的總時間(以時鐘秒爲單位)。
  3. 依次執行了全部五個測試,而後又進行了三次。
  4. 平均每種方法的四個結果。

結果

我將首先提供結果,併爲感興趣的人提供如下代碼。 ide

如所預期的, ContainsKey方法是最慢的,所以,與該方法的速度相比,我將給出每種方法的速度。 性能

  • ContainsKey: 30.654秒(基準)
  • AtomicLong: 29.780秒(速度的1.03倍)
  • TestForNull: 28.804秒(速度的1.06倍)
  • 寶座 26.313秒(1.16倍的速度)
  • MutableInt: 25.747秒(1.19倍的速度)

結論

彷佛只有MutableInt方法和Trove方法要快得多,由於它們的性能提高超過10%。 可是,若是線程成爲問題,AtomicLong可能比其餘線程更具吸引力(我不肯定)。 我還運行了帶有final變量的TestForNull,可是差別能夠忽略不計。

請注意,我沒有介紹不一樣狀況下的內存使用狀況。 我很高興聽到任何對MutableInt和Trove方法將如何影響內存使用有深入看法的人。

我我的認爲MutableInt方法最吸引人,由於它不須要加載任何第三方類。 所以,除非我發現問題,不然這是我最有可能採起的方法。

編碼

這是每種方法的關鍵代碼。

ContainsKey

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
int count = freq.containsKey(word) ? freq.get(word) : 0;
freq.put(word, count + 1);

TestForNull

import java.util.HashMap;
import java.util.Map;
...
Map<String, Integer> freq = new HashMap<String, Integer>();
...
Integer count = freq.get(word);
if (count == null) {
    freq.put(word, 1);
}
else {
    freq.put(word, count + 1);
}

原子長

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
...
final ConcurrentMap<String, AtomicLong> map = 
    new ConcurrentHashMap<String, AtomicLong>();
...
map.putIfAbsent(word, new AtomicLong(0));
map.get(word).incrementAndGet();

寶藏

import gnu.trove.TObjectIntHashMap;
...
TObjectIntHashMap<String> freq = new TObjectIntHashMap<String>();
...
freq.adjustOrPutValue(word, 1, 1);

MutableInt

import java.util.HashMap;
import java.util.Map;
...
class MutableInt {
  int value = 1; // note that we start at 1 since we're counting
  public void increment () { ++value;      }
  public int  get ()       { return value; }
}
...
Map<String, MutableInt> freq = new HashMap<String, MutableInt>();
...
MutableInt count = freq.get(word);
if (count == null) {
    freq.put(word, new MutableInt());
}
else {
    count.increment();
}

#2樓

MutableInt方法的一種變體是使用單元素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);

#3樓

Google Guava是您的朋友...

...至少在某些狀況下。 他們有這個漂亮的AtomicLongMap 。 特別好,由於您正在處理地圖中的長期價值。

例如

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

也能夠將值加1以上:

map.getAndAdd(word, 112L);

#4樓

若是您使用的是Eclipse Collections ,則可使用HashBag 。 就內存使用而言,這將是最有效的方法,而且在執行速度方面也將表現良好。

HashBagHashBag支持,該MutableObjectIntMap存儲原始int而不是Counter對象。 這樣能夠減小內存開銷並提升執行速度。

HashBag提供了所需的API,由於它是一個Collection ,它還容許您查詢項目的出現次數。

這是Eclipse Collections Kata的示例。

MutableBag<String> bag =
  HashBag.newBagWith("one", "two", "two", "three", "three", "three");

Assert.assertEquals(3, bag.occurrencesOf("three"));

bag.add("one");
Assert.assertEquals(2, bag.occurrencesOf("one"));

bag.addOccurrences("one", 4);
Assert.assertEquals(6, bag.occurrencesOf("one"));

注意:我是Eclipse Collections的提交者。


#5樓

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

時間/空間結果:

相關文章
相關標籤/搜索