我但願這個問題對於本論壇來講不是太基本了,可是咱們會看到的。 我想知道如何重構一些代碼以得到更好的性能,而這些性能已經運行了不少次。 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
更新:我已經測試了幾個答案。 見下文。 數組
對於這個問題,我已經獲得了不少很好的答案-謝謝你們-所以,我決定進行一些測試,找出哪一種方法實際上最快。 我測試的五種方法是: 框架
這是我作的... eclipse
我將首先提供結果,併爲感興趣的人提供如下代碼。 ide
如所預期的, ContainsKey方法是最慢的,所以,與該方法的速度相比,我將給出每種方法的速度。 性能
彷佛只有MutableInt方法和Trove方法要快得多,由於它們的性能提高超過10%。 可是,若是線程成爲問題,AtomicLong可能比其餘線程更具吸引力(我不肯定)。 我還運行了帶有final
變量的TestForNull,可是差別能夠忽略不計。
請注意,我沒有介紹不一樣狀況下的內存使用狀況。 我很高興聽到任何對MutableInt和Trove方法將如何影響內存使用有深入看法的人。
我我的認爲MutableInt方法最吸引人,由於它不須要加載任何第三方類。 所以,除非我發現問題,不然這是我最有可能採起的方法。
這是每種方法的關鍵代碼。
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);
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);
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(); }
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);
...至少在某些狀況下。 他們有這個漂亮的AtomicLongMap 。 特別好,由於您正在處理地圖中的長期價值。
例如
AtomicLongMap<String> map = AtomicLongMap.create(); [...] map.getAndIncrement(word);
也能夠將值加1以上:
map.getAndAdd(word, 112L);
若是您使用的是Eclipse Collections ,則可使用HashBag
。 就內存使用而言,這將是最有效的方法,而且在執行速度方面也將表現良好。
HashBag
由HashBag
支持,該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的提交者。
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
時間/空間結果: