当前位置:编程学习 > JAVA >>

Java高效计数器

我们经常使用 HashMap作为计数器(counter)来统计数据库或者文本中的某些东西.
本文将使用HashMap来实现计数器的3种不同方式进行对比。
 
1. 新手级计数器
如果使用这一类别的计数器,那么代码大致如下所示:
[java]  
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]  
// 可变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]  
// 入门级计数器  
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] 
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] 
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垃圾回收,同时也证明了垃圾回收的代价确实很大。
 
完整的测试代码如下:
[java] 
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;  
  &nbs
补充:软件开发 , Java ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,