如何高效的实现一个计数器map(一)

2014-11-24 11:20:10 · 作者: · 浏览: 9
这本是多年前一个stackoverflow上的一个讨论,回答中涉及到了多种计数方法。对于一个key-value结构的map,我们在编程时会经常涉及到key是对象,而value是一个integer或long来负责计数,从而统计多个key的频率。
面对这样一个基本需求,可能有很多种实现。比如最基本的使用jdk的map直接实现——value是一个integer或者long。其基本代码型如下:
1: final Map freq = new HashMap();
2: int count = freq.containsKey(word) freq.get(word) : 0;
3: freq.put(word, count + 1);
逻辑简单,判断是否存在,是则get取值,否则为0,再put进去一个加1后的值。总共要contain判断,get,put做三次方法调用。
当然进一步我们可以把contain判断去掉,代码如下:
1: final Map freq = new HashMap();
2: Integer count = freq.get(word);
3: if (count == null) {
4: freq.put(word, 1);
5: } else {
6: freq.put(word, count + 1);
7: }
一般情况,我们做到这个地步,多数人对其逻辑已经满足,简单性能也能接受,试着想一下,难道不是这样吗?get加put,解决了。
当然这样的实现还不够高效,于是我们开始去尝试实现或寻找更高效的方法,看看开源的集合类库是否有需要的:
有个Trove,可以让我们参考一下:
1: final TObjectIntHashMap freq = new TObjectIntHashMap();
2: freq.adjustOrPutValue(word, 1, 1);
这样做,非常优雅啊,性能如何呢?不知道,需要看 源码了解细节。那再看看大名鼎鼎的guava如何呢?
1: AtomicLongMap map = AtomicLongMap.create();
2: map.getAndIncrement(word);
实现依然优雅,但是,但是看这名字,再看源码,好吧,线程安全的,支持并发,这不好搞了,我们场景需要吗?不需要的话直觉告诉我们这肯定是“慢”的。再找:
1: Multiset bag = HashMultiset.create();
2: bag.add(word);
这个看上去合适了,bag的实现明显好很多,而且从语义理解上,这样的接口更容易让人理解。
那么这些方法,性能如何呢?做了个简单的比较,将26个英文字母做key,均匀循环若干次比较各个方法的效率(单纯时间效率),而且时间不统计构建开销。外加了一个线程安全版的concurrentMap实现,而其实google的guava里的AtomicLongMap也是包装了juc的concurrentMap而已。里面有最终极的MutableInt方法,找找看吧,性能最好的就是它了。
1: /**
2: *
3: */
4:
5:
6: import gnu.trove.map.hash.TObjectIntHashMap;
7:
8: import java.util.HashMap;
9: import java.util.Map;
10: import java.util.concurrent.ConcurrentHashMap;
11: import java.util.concurrent.ConcurrentMap;
12: import java.util.concurrent.atomic.AtomicLong;
13:
14: import com.google.common.collect.HashMultiset;
15: import com.google.common.collect.Multiset;
16: import com.google.common.util.concurrent.AtomicLongMap;
17:
18: /**
19: * @author Administrator
20: *
21: */
22: public class IntMapTest {
23:
24: /**
25: * @param args
26: */
27: public static void main(String[] args) {
28: // TODO Auto-generated method stub
29: int cycles[] = { 100, 1000, 10000, 100000 };
30: Tester baseLine = new BaseLine();
31: Tester testForNull = new UseNullTest();
32: Tester useAtomicLong = new UseAtomicLong();
33: Tester useTrove = new UseTrove();
34: Tester useMutableInt = new UseMutableInt();
35: Tester useGuava = new UseGuava();
36: Tester useGuava2 = new UseGuava2();
37:
38: for (int i = 0; i < cycles.length; i++) {
39: System.out.println("-----With " + cycles[i] + " cycles-----");
40: baseLine.test(cycles[i]);
41: testForNull.test(cycles[i]);
42: useAtomicLong.test(cycles[i]);
43: useTrove.test(cycles[i]);
44: useMutableInt.test(cycles[i]);
45: useGuava.test(cycles[i]);
46: useGuava2.test(cycles[i]);
47: System.out.println("------------------------");
48: }
49:
50: }
51:
52: }
53:
54: abstract class Tester {
55: long ms;
56: static String[