Java高效计数器
本文轉載地址: ? ? ? ? ? ? ?http://blog.csdn.net/renfufei/article/details/14120775
我們經常使用 HashMap作為計數器(counter)來統計數據庫或者文本中的某些東西.
本文將使用HashMap來實現計數器的3種不同方式進行對比。
1. 新手級計數器
如果使用這一類別的計數器,那么代碼大致如下所示:
[java] view plaincopyprint?String?source?=?"my?name?is?name?me?and?your?name?is?her?first?her";?? String[]?words?=?source.split("?");?? //?新手級計數器?? public?static?void?testNaive(String[]?words){?? ????HashMap<String,?Integer>?counter?=?new?HashMap<String,?Integer>();?? ????for?(String?w?:?words)?{?? ????????if(counter.containsKey(w)){?? ????????????int?oldValue?=?counter.get(w);?? ????????????counter.put(w,?oldValue+1);?? ????????}?else?{?? ????????????counter.put(w,?1);?? ????????}?? ????}?? } ?
在每次循環中,判斷是否包含了相應的key,如果包含,那么值在原來的基礎上加1,如果沒有,那就設置為1.
此種方式簡單又直接,但并不是很有效率。效率不高的原因如下:
1.1 當一個key存在時,containsKey() 和 get() 分別調用了一次,這意味著對map進行了兩次查找。
1.2 因為 Integer 是不可變的,每次循環在增加計數值的時候將會創建一個新的對象.
2. 入門級計數器
那么我們自然需要使用一個可變的整數來避免創建太多個Integer對象.可變整數類可以如下面所示來定義:
[java] view plaincopyprint?//?可變Integer?? public?static?final?class?MutableInteger{?? ????private?int?val;?? ????public?MutableInteger(int?val){?? ????????this.val?=?val;?? ????}?? ????public?int?get(){?? ????????return?this.val;?? ????}?? ????public?void?set(int?val){?? ????????this.val?=?val;?? ????}?? ????//?為了方便打印?? ????public?String?toString()?{?? ????????return?Integer.toString(val);?? ????}?? } ?
那么計數器可以用如下的方式來改進:
[java] view plaincopyprint?//?入門級計數器?? public?static?void?testBetter(String[]?words){?? ????HashMap<String,?MutableInteger>?counter?=?new?HashMap<String,?MutableInteger>();?? ????for?(String?w?:?words)?{?? ????????if(counter.containsKey(w)){?? ????????????MutableInteger?oldValue?=?counter.get(w);?? ????????????oldValue.set(oldValue.get()+1);?//?因為是引用,所以減少了一次HashMap查找?? ????????}?else?{?? ????????????counter.put(w,?new?MutableInteger(1));?? ????????}?? ????}?? } ?
因為不需要創建太多的Integer對象,看起來好了一些。然而,key存在的情況下,每次循環依然要進行兩次查找.
3. 卓越級計數器
HashMap 的 put(key,value) 方法會返回key對應的當前value.了解這個特性,我們可以利用原有值來進行遞增,并不需要多次的查找.
[java] view plaincopyprint?public?static?void?testEfficient(String[]?words){?? ????HashMap<String,?MutableInteger>?counter?=?new?HashMap<String,?MutableInteger>();?? ????for?(String?w?:?words)?{?? ????????MutableInteger?initValue?=?new?MutableInteger(1);?? ????????//?利用?HashMap?的put方法彈出舊值的特性?? ????????MutableInteger?oldValue?=?counter.put(w,?initValue);?? ????????if(oldValue?!=?null){?? ????????????initValue.set(oldValue.get()?+?1);?? ????????}?? ????}?? } ?
4. 性能差異
為了測試這三種實現方式的性能,采用了下面的代碼。先看看結果如何,性能測試分別執行了多次,對每一個數量級的測試,誤差不算太大,所以取其中的一個結果排列如下:
[plain] view plaincopyprint?10000000?次循環:?? 新手級計數器:?7726594902?? 入門級計數器:?6516014840?? 卓越級計數器:?5736574103?? ?? ?? 1000000?次循環:?? 新手級計數器:?777480106?? 入門級計數器:?642932000?? 卓越級計數器:?571867738?? ?? ?? 100000?次循環:?? 新手級計數器:?84323682?? 入門級計數器:?70176906?? 卓越級計數器:?61219664?? ?? ?? 10000?次循環:?? 新手級計數器:?13279550?? 入門級計數器:?7874100?? 卓越級計數器:?6460172?? ?? ?? 1000?次循環:?? 新手級計數器:?4542172?? 入門級計數器:?2933248?? 卓越級計數器:?992749?? ?? ?? 100?次循環:?? 新手級計數器:?3092325?? 入門級計數器:?1101695?? 卓越級計數器:?423942?? ?? ?? 10?次循環:?? 新手級計數器:?1993788?? 入門級計數器:?558150?? 卓越級計數器:?153156?? ?? ?? 1?次循環:?? 新手級計數器:?1625898?? 入門級計數器:?427494?? 卓越級計數器:?69473 ?
從上面的輸出可以看到,10000次的時候, 13:8:6 秒,相差很明顯.特別是 新手級計數器和入門級計數器之間的比例,這說明創建對象是很耗資源的操作。
當然,次數更多的差距不明顯的原因在于,觸發了多次的GC垃圾回收,同時也證明了垃圾回收的代價確實很大。
完整的測試代碼如下:
import?java.util.HashMap;?? ?? public?class?TestCounter?{?? ?????? ????public?static?void?main(String[]?args)?{?? ????????//?源字符串?? ????????String?source?=?"my?name?is?name?me?and?your?name?is?her?first?her";?? ????????//?計時,單位:?微秒?? ????????long?startTime?=?0;?? ????????long?endTime?=?0;?? ????????long?duration?=?0;?? ????????//?測試次數?? ????????int?loop?=?1?*?10000;?? ?? ????????System.out.println(loop?+"?次循環:");?? ????????startTime?=?System.nanoTime();?? ????????testNaive(source,loop);?? ????????endTime?=?System.nanoTime();?? ????????duration?=?endTime?-?startTime;?? ????????System.out.println("新手級計數器:?"?+?duration);?? ????????//?? ????????startTime?=?System.nanoTime();?? ????????testBetter(source,?loop);?? ????????endTime?=?System.nanoTime();?? ????????duration?=?endTime?-?startTime;?? ????????System.out.println("入門級計數器:?"?+?duration);?? ????????//?? ????????startTime?=?System.nanoTime();?? ????????testEfficient(source,?loop);?? ????????endTime?=?System.nanoTime();?? ????????duration?=?endTime?-?startTime;?? ????????System.out.println("卓越級計數器:?"?+?duration);?? ????}?? ?? ????//?新手級計數器?? ????public?static?void?testNaive(String?source,?int?loop){?? ????????if(null?==?source){?? ????????????return;?? ????????}?? ????????//?? ????????String[]?words?=?source.split("?");?? ????????for?(int?i?=?0;?i?<?loop;?i++)?{?? ????????????testNaive(words);?? ????????}?? ????}?? ????public?static?void?testNaive(String[]?words){?? ????????HashMap<String,?Integer>?counter?=?new?HashMap<String,?Integer>();?? ????????for?(String?w?:?words)?{?? ????????????if(counter.containsKey(w)){?? ????????????????int?oldValue?=?counter.get(w);?? ????????????????counter.put(w,?oldValue+1);?? ????????????}?else?{?? ????????????????counter.put(w,?1);?? ????????????}?? ????????}?? ????}?? ????//?可變Integer?? ????public?static?final?class?MutableInteger{?? ????????private?int?val;?? ????????public?MutableInteger(int?val){?? ????????????this.val?=?val;?? ????????}?? ????????public?int?get(){?? ????????????return?this.val;?? ????????}?? ????????public?void?set(int?val){?? ????????????this.val?=?val;?? ????????}?? ????????//?為了方便打印?? ????????public?String?toString()?{?? ????????????return?Integer.toString(val);?? ????????}?? ????}?? ?????? ????//?入門級計數器?? ????public?static?void?testBetter(String?source,?int?loop){?? ????????if(null?==?source){?? ????????????return;?? ????????}?? ????????//?? ????????String[]?words?=?source.split("?");?? ????????for?(int?i?=?0;?i?<?loop;?i++)?{?? ????????????testBetter(words);?? ????????}?? ????}?? ????public?static?void?testBetter(String[]?words){?? ????????HashMap<String,?MutableInteger>?counter?=?new?HashMap<String,?MutableInteger>();?? ????????for?(String?w?:?words)?{?? ????????????if(counter.containsKey(w)){?? ????????????????MutableInteger?oldValue?=?counter.get(w);?? ????????????????oldValue.set(oldValue.get()+1);?//?因為是引用,所以減少了一次HashMap查找?? ????????????}?else?{?? ????????????????counter.put(w,?new?MutableInteger(1));?? ????????????}?? ????????}?? ????}?? ?????? ????//?卓越級計數器?? ????public?static?void?testEfficient(String?source,?int?loop){?? ????????if(null?==?source){?? ????????????return;?? ????????}?? ????????//?? ????????String[]?words?=?source.split("?");?? ????????for?(int?i?=?0;?i?<?loop;?i++)?{?? ????????????testEfficient(words);?? ????????}?? ????}?? ????public?static?void?testEfficient(String[]?words){?? ????????HashMap<String,?MutableInteger>?counter?=?new?HashMap<String,?MutableInteger>();?? ????????for?(String?w?:?words)?{?? ????????????MutableInteger?initValue?=?new?MutableInteger(1);?? ????????????//?利用?HashMap?的put方法彈出舊值的特性?? ????????????MutableInteger?oldValue?=?counter.put(w,?initValue);?? ????????????if(oldValue?!=?null){?? ????????????????initValue.set(oldValue.get()?+?1);?? ????????????}?? ????????}?? ????}?? } ?
當你實用計數器的時候,很可能也需要根據值來進行排序的方法,請參考: the frequently used method of HashMap.
5. Keith網站評論列表
我覺得最好的評論如下:
添加了三個測試:
1) 重構了 “入門級計數器”,不使用containsKey,改為只使用get方法. 通常你需要的元素是存在于 HashMap 中的, 所以將 2 次查找精簡為 1次.
2) 作者 michal 提到過的方式,使用 AtomicInteger來實現 .
3) 使用單個的int 數組來進行對比,可以使用更少的內存,參見 http://amzn.com/0748614079
我運行了測試程序3次,并挑選出最小的那個值(以減少干擾). 注意: 你不能在程序中讓運行結果受到太多干擾,因為內存不足可能會受到gc垃圾回收器太多的影響.
新手級計數器: 201716122
入門級計數器: 112259166
卓越級計數器: 93066471
入門級計數器 (不使用 containsKey): 69578496
入門級計數器 (不使用 containsKey, with AtomicInteger): 94313287
入門級計數器 (不使用 containsKey, with int[]): 65877234
入門級計數器 (不使用 containsKey 方法:):
[java] view plaincopyprint?HashMap<string,?mutableinteger="">?efficientCounter2?=?new?HashMap<string,?mutableinteger="">();?? for?(int?i?=?0;?i?<?NUM_ITERATIONS;?i++)?? for?(String?a?:?sArr)?{?? MutableInteger?value?=?efficientCounter2.get(a);?? ??? if?(value?!=?null)?{?? value.set(value.get()?+?1);?? }?? else?{?? efficientCounter2.put(a,?new?MutableInteger(1));?? }?? } ? 入門級計數器 (不使用 containsKey, 使用 AtomicInteger):
[java] view plaincopyprint?HashMap<string,?atomicinteger="">?atomicCounter?=?new?HashMap<string,?atomicinteger="">();?? for?(int?i?=?0;?i?<?NUM_ITERATIONS;?i++)?? for?(String?a?:?sArr)?{?? AtomicInteger?value?=?atomicCounter.get(a);?? ??? if?(value?!=?null)?{?? value.incrementAndGet();?? }?? else?{?? atomicCounter.put(a,?new?AtomicInteger(1));?? }?? } ?
入門級計數器 (不使用 containsKey, 使用 ?int[]):
[java] view plaincopyprint?HashMap<string,?int[]="">?intCounter?=?new?HashMap<string,?int[]="">();?? for?(int?i?=?0;?i?<?NUM_ITERATIONS;?i++)?? for?(String?a?:?sArr)?{?? int[]?valueWrapper?=?intCounter.get(a);?? ??? if?(valueWrapper?==?null)?{?? intCounter.put(a,?new?int[]?{?1?});?? }?? else?{?? valueWrapper[0]++;?? }?? } ?
Guava 語言的 MultiSet 可能更快一些.
6. 結論
優勝者是使用int數組的方式.
我們經常使用 HashMap作為計數器(counter)來統計數據庫或者文本中的某些東西.
本文將使用HashMap來實現計數器的3種不同方式進行對比。
1. 新手級計數器
如果使用這一類別的計數器,那么代碼大致如下所示:
[java] view plaincopyprint?
在每次循環中,判斷是否包含了相應的key,如果包含,那么值在原來的基礎上加1,如果沒有,那就設置為1.
此種方式簡單又直接,但并不是很有效率。效率不高的原因如下:
1.1 當一個key存在時,containsKey() 和 get() 分別調用了一次,這意味著對map進行了兩次查找。
1.2 因為 Integer 是不可變的,每次循環在增加計數值的時候將會創建一個新的對象.
2. 入門級計數器
那么我們自然需要使用一個可變的整數來避免創建太多個Integer對象.可變整數類可以如下面所示來定義:
[java] view plaincopyprint?
那么計數器可以用如下的方式來改進:
[java] view plaincopyprint?
因為不需要創建太多的Integer對象,看起來好了一些。然而,key存在的情況下,每次循環依然要進行兩次查找.
3. 卓越級計數器
HashMap 的 put(key,value) 方法會返回key對應的當前value.了解這個特性,我們可以利用原有值來進行遞增,并不需要多次的查找.
[java] view plaincopyprint?
4. 性能差異
為了測試這三種實現方式的性能,采用了下面的代碼。先看看結果如何,性能測試分別執行了多次,對每一個數量級的測試,誤差不算太大,所以取其中的一個結果排列如下:
[plain] view plaincopyprint?
從上面的輸出可以看到,10000次的時候, 13:8:6 秒,相差很明顯.特別是 新手級計數器和入門級計數器之間的比例,這說明創建對象是很耗資源的操作。
當然,次數更多的差距不明顯的原因在于,觸發了多次的GC垃圾回收,同時也證明了垃圾回收的代價確實很大。
完整的測試代碼如下:
?
[java] view plaincopyprint?當你實用計數器的時候,很可能也需要根據值來進行排序的方法,請參考: the frequently used method of HashMap.
?
5. Keith網站評論列表
我覺得最好的評論如下:
添加了三個測試:
1) 重構了 “入門級計數器”,不使用containsKey,改為只使用get方法. 通常你需要的元素是存在于 HashMap 中的, 所以將 2 次查找精簡為 1次.
2) 作者 michal 提到過的方式,使用 AtomicInteger來實現 .
3) 使用單個的int 數組來進行對比,可以使用更少的內存,參見 http://amzn.com/0748614079
我運行了測試程序3次,并挑選出最小的那個值(以減少干擾). 注意: 你不能在程序中讓運行結果受到太多干擾,因為內存不足可能會受到gc垃圾回收器太多的影響.
新手級計數器: 201716122
入門級計數器: 112259166
卓越級計數器: 93066471
入門級計數器 (不使用 containsKey): 69578496
入門級計數器 (不使用 containsKey, with AtomicInteger): 94313287
入門級計數器 (不使用 containsKey, with int[]): 65877234
入門級計數器 (不使用 containsKey 方法:):
[java] view plaincopyprint?
[java] view plaincopyprint?
入門級計數器 (不使用 containsKey, 使用 ?int[]):
[java] view plaincopyprint?
Guava 語言的 MultiSet 可能更快一些.
6. 結論
優勝者是使用int數組的方式.
轉載于:https://www.cnblogs.com/hthuang/p/4371515.html
總結
- 上一篇: GCD之after
- 下一篇: java美元兑换,(Java实现) 美元