zoukankan      html  css  js  c++  java
  • Java实现的高效计数器

    本文转载地址:

               http://blog.csdn.net/snarlfuture/article/details/17049731


        在统计来自数据库或文本中某些内容的频率时,你可能经常会用到HashMap。本文对比了三种用HashMap实现的计数器。

    1. 简单的计数器

        如果你使用这样一个计数器,你的代码可能如下:

    1. String s = "one two three two three three";  
    2. String[] sArr = s.split(" ");  
    3.   
    4. //naive approach  
    5. HashMap<String, Integer> counter = new HashMap<String, Integer>();  
    6.   
    7. for(String a : sArr) {  
    8.     if(counter.containsKey(a)) {  
    9.         int oldValue = counter.get(a);  
    10.         counter.put(a, oldValue+1);  
    11.     } else {  
    12.         counter.put(a, 1);  
    13.     }  
    14. }  
        每次循环,你都要判断键(key)是否存在。如果该键存在,你需要键对应的值加1,否则,这设置对应的值为1。该方法看起来简单而直接,但它并不是最有效率的方法,它在如下方面欠考虑:

    ① 如果键(key)已经存在的话,containsKey()、get()就会方法被调用两次,这意味着要搜索map两次;

    ② 由于整数(Integer)是不可变的,每次循环都会创建一个新的整数对象保存新的计数值。

    2. 改进的计数器

        自然而然的,我们希望用一个可变的整数值来避免创建过多的整数对象。因此,可以定义一个可变整数类,如下所示:

    1. class MutableInteger {  
    2.   
    3.     private int val;  
    4.       
    5.     public MutableInteger(int val) {  
    6.         this.val = val;  
    7.     }  
    8.   
    9.     public int get() {  
    10.         return val;  
    11.     }  
    12.   
    13.     public void set(int val) {  
    14.         this.val = val;  
    15.     }  
    16.   
    17.     //used to print value convinently  
    18.     public String toString() {  
    19.         return Integer.toString(val);  
    20.     }  
    21. }  
        改进后的计数器如下所示:
    1. HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>();  
    2.   
    3. for(String a : sArr) {  
    4.     if(newCounter.containsKey(a)) {  
    5.         MutableInteger oldValue = newCounter.get(a);  
    6.         oldValue.set(oldValue.get() + 1);  
    7.     } else {  
    8.         newCounter.put(a, new MutableInteger(1));  
    9.     }  
    10. }  
        改进后的计数器无需创建大量的整数(Integer)对象,效率有所提高,但是它还有没有解决的问题:当键(key)存在时需要搜索两次map。

    3. 高效的计数器

        HashMap.put(key, value)方法返回键(key)对应的值。这个方法很有用,我们可以直接使用旧值的引用来更新值,而不需要再多进行一次搜索。

    1. HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>();  
    2.   
    3. for(String a : sArr) {  
    4.     MutableInteger initValue = new MutableInteger(1);  
    5.     MutableInteger oldValue = efficientCounter.put(a, initValue);  
    6.   
    7.     if(oldValue != null) {  
    8.         initValue.set(oldValue.get() + 1);  
    9.     }  
    10. }  

    4. 性能差异

        可以使用下面的代码来测试上述三种方法在性能上的差异。性能测试循环次数为1百万次,实验结果如下所示:

    1. Naive Approach : 222796000  
    2. Better Approach: 117283000  
    3. Efficient Approach: 96374000  

        三种方法在性能上的差异是十分显著的:223 vs. 117 vs. 96。最原始的计数器和优化后的计数器之间的性能差异十分明显,这意味着创建对象的开销是十分昂贵的。

    1. String s = "one two three two three three";  
    2. String[] sArr = s.split(" ");  
    3.    
    4. long startTime = 0;  
    5. long endTime = 0;  
    6. long duration = 0;  
    7.    
    8. // naive approach  
    9. startTime = System.nanoTime();  
    10. HashMap<String, Integer> counter = new HashMap<String, Integer>();  
    11.    
    12. for (int i = 0; i < 1000000; i++)  
    13.     for (String a : sArr) {  
    14.         if (counter.containsKey(a)) {  
    15.             int oldValue = counter.get(a);  
    16.             counter.put(a, oldValue + 1);  
    17.         } else {  
    18.             counter.put(a, 1);  
    19.         }  
    20.     }  
    21.    
    22. endTime = System.nanoTime();  
    23. duration = endTime - startTime;  
    24. System.out.println("Naive Approach :  " + duration);  
    25.    
    26. // better approach  
    27. startTime = System.nanoTime();  
    28. HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>();  
    29.    
    30. for (int i = 0; i < 1000000; i++)  
    31.     for (String a : sArr) {  
    32.         if (newCounter.containsKey(a)) {  
    33.             MutableInteger oldValue = newCounter.get(a);  
    34.             oldValue.set(oldValue.get() + 1);  
    35.         } else {  
    36.             newCounter.put(a, new MutableInteger(1));  
    37.         }  
    38.     }  
    39.    
    40. endTime = System.nanoTime();  
    41. duration = endTime - startTime;  
    42. System.out.println("Better Approach:  " + duration);  
    43.    
    44. // efficient approach  
    45. startTime = System.nanoTime();  
    46.    
    47. HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>();  
    48.    
    49. for (int i = 0; i < 1000000; i++)  
    50.     for (String a : sArr) {  
    51.         MutableInteger initValue = new MutableInteger(1);  
    52.         MutableInteger oldValue = efficientCounter.put(a, initValue);  
    53.    
    54.         if (oldValue != null) {  
    55.             initValue.set(oldValue.get() + 1);  
    56.         }  
    57.     }  
    58.    
    59. endTime = System.nanoTime();  
    60. duration = endTime - startTime;  
    61. System.out.println("Efficient Approach:  " + duration);  
        当你使用计数器时,你可能需要使用一个方法来根据值(value)对map进行排序,对此,你可以参照文章《HashMap中常用的方法》

    5. Keith的评论(如下所示)

        下面是我收到的最好的评论之一。

        添加下面一系列测试:

        1) 重构上述”改进的计数器“,用get()方法来替换containsKey()方法。通常,所需的元素都在HashMap中,因此可以将搜索次数从两次减少到一次。

        2) Michal提到了AtuomicInteger,下面也进行了相关的试验。

        3) 与单例的int数组相比,http://amzn.com/0748614079中提到这可能会使用更少的内存。

        我运行了测试程序3x次,争取每次对代码的改变都最小。需要注意的是,你可能无法做到在程序中做到上述改动,或者试验结果受影响较大,原因可能是垃圾回收期。

    1. Naive: 201716122  
    2. Better Approach: 112259166  
    3. Efficient Approach: 93066471  
    4. Better Approach (without containsKey): 69578496  
    5. Better Approach (without containsKey, with AtomicInteger): 94313287  
    6. Better Approach (without containsKey, with int[]): 65877234  
        改进的计数器(不使用containsKey()):
    1. HashMap<string, mutableinteger=""> efficientCounter2 = new HashMap<string, mutableinteger="">();  
    2. for (int i = 0; i < NUM_ITERATIONS; i++)  
    3.     for (String a : sArr) {  
    4.         MutableInteger value = efficientCounter2.get(a);  
    5.    
    6.         if (value != null) {  
    7.             value.set(value.get() + 1);  
    8.         } else {  
    9.             efficientCounter2.put(a, new MutableInteger(1));  
    10.         }  
    11.     }  
        改进的计数器(不使用containskey(),使用AtomicInteger):
    1. HashMap<string, atomicinteger=""> atomicCounter = new HashMap<string, atomicinteger="">();  
    2. for (int i = 0; i < NUM_ITERATIONS; i++)  
    3.     for (String a : sArr) {  
    4.         AtomicInteger value = atomicCounter.get(a);  
    5.    
    6.         if (value != null) {  
    7.             value.incrementAndGet();  
    8.         } else {  
    9.             atomicCounter.put(a, new AtomicInteger(1));  
    10.         }  
    11.     }  
        改进的计数器(不使用containsKey(),使用int[]):
    1. HashMap<string, int[]=""> intCounter = new HashMap<string, int[]="">();  
    2. for (int i = 0; i < NUM_ITERATIONS; i++)  
    3.     for (String a : sArr) {  
    4.         int[] valueWrapper = intCounter.get(a);  
    5.   
    6.         if (valueWrapper == null) {  
    7.             intCounter.put(a, new int[] { 1 });  
    8.         } else {  
    9.             valueWrapper[0]++;  
    10.         }  
    11.     }  
        Guava的MultiSet可能更快。

    6. 总结

        性能最高的是使用int数组的那个方法。

  • 相关阅读:
    Swift3.0 数组(Array)
    Swift3.0 UICollectionView简单使用
    Swift3.0 字符串(string)
    Swift3.0 元组 (tuples)
    Swift3.0 UICollectionView 删除,拖动
    Swift3.0 控制流
    Swift3.0 UITextView写反馈界面
    HashMap JDK1.8实现原理
    Volatile的详解
    阻塞队列之LinkedBlockingQueue
  • 原文地址:https://www.cnblogs.com/hthuang/p/4372753.html
Copyright © 2011-2022 走看看