Java实现的高效计数器
2013-11-30 23:55
543 查看
在统计来自数据库或文本中某些内容的频率时,你可能经常会用到HashMap。本文对比了三种用HashMap实现的计数器。
① 如果键(key)已经存在的话,containsKey()、get()就会方法被调用两次,这意味着要搜索map两次;
② 由于整数(Integer)是不可变的,每次循环都会创建一个新的整数对象保存新的计数值。
三种方法在性能上的差异是十分显著的:223 vs. 117 vs. 96。最原始的计数器和优化后的计数器之间的性能差异十分明显,这意味着创建对象的开销是十分昂贵的。
添加下面一系列测试:
1) 重构上述”改进的计数器“,用get()方法来替换containsKey()方法。通常,所需的元素都在HashMap中,因此可以将搜索次数从两次减少到一次。
2) Michal提到了AtuomicInteger,下面也进行了相关的试验。
3) 与单例的int数组相比,http://amzn.com/0748614079中提到这可能会使用更少的内存。
我运行了测试程序3x次,争取每次对代码的改变都最小。需要注意的是,你可能无法做到在程序中做到上述改动,或者试验结果受影响较大,原因可能是垃圾回收期。
原文地址:Efficient Counter in Java
1. 简单的计数器
如果你使用这样一个计数器,你的代码可能如下:String s = "one two three two three three"; String[] sArr = s.split(" "); //naive approach HashMap<String, Integer> counter = new HashMap<String, Integer>(); for(String a : sArr) { if(counter.containsKey(a)) { int oldValue = counter.get(a); counter.put(a, oldValue+1); } else { counter.put(a, 1); } }每次循环,你都要判断键(key)是否存在。如果该键存在,你需要键对应的值加1,否则,这设置对应的值为1。该方法看起来简单而直接,但它并不是最有效率的方法,它在如下方面欠考虑:
① 如果键(key)已经存在的话,containsKey()、get()就会方法被调用两次,这意味着要搜索map两次;
② 由于整数(Integer)是不可变的,每次循环都会创建一个新的整数对象保存新的计数值。
2. 改进的计数器
自然而然的,我们希望用一个可变的整数值来避免创建过多的整数对象。因此,可以定义一个可变整数类,如下所示:class MutableInteger { private int val; public MutableInteger(int val) { this.val = val; } public int get() { return val; } public void set(int val) { this.val = val; } //used to print value convinently public String toString() { return Integer.toString(val); } }改进后的计数器如下所示:
HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>(); for(String a : sArr) { if(newCounter.containsKey(a)) { MutableInteger oldValue = newCounter.get(a); oldValue.set(oldValue.get() + 1); } else { newCounter.put(a, new MutableInteger(1)); } }改进后的计数器无需创建大量的整数(Integer)对象,效率有所提高,但是它还有没有解决的问题:当键(key)存在时需要搜索两次map。
3. 高效的计数器
HashMap.put(key, value)方法返回键(key)对应的值。这个方法很有用,我们可以直接使用旧值的引用来更新值,而不需要再多进行一次搜索。HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>(); for(String a : sArr) { MutableInteger initValue = new MutableInteger(1); MutableInteger oldValue = efficientCounter.put(a, initValue); if(oldValue != null) { initValue.set(oldValue.get() + 1); } }
4. 性能差异
可以使用下面的代码来测试上述三种方法在性能上的差异。性能测试循环次数为1百万次,实验结果如下所示:Naive Approach : 222796000 Better Approach: 117283000 Efficient Approach: 96374000
三种方法在性能上的差异是十分显著的:223 vs. 117 vs. 96。最原始的计数器和优化后的计数器之间的性能差异十分明显,这意味着创建对象的开销是十分昂贵的。
String s = "one two three two three three"; String[] sArr = s.split(" "); long startTime = 0; long endTime = 0; long duration = 0; // naive approach startTime = System.nanoTime(); HashMap<String, Integer> counter = new HashMap<String, Integer>(); for (int i = 0; i < 1000000; i++) for (String a : sArr) { if (counter.containsKey(a)) { int oldValue = counter.get(a); counter.put(a, oldValue + 1); } else { counter.put(a, 1); } } endTime = System.nanoTime(); duration = endTime - startTime; System.out.println("Naive Approach : " + duration); // better approach startTime = System.nanoTime(); HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>(); for (int i = 0; i < 1000000; i++) for (String a : sArr) { if (newCounter.containsKey(a)) { MutableInteger oldValue = newCounter.get(a); oldValue.set(oldValue.get() + 1); } else { newCounter.put(a, new MutableInteger(1)); } } endTime = System.nanoTime(); duration = endTime - startTime; System.out.println("Better Approach: " + duration); // efficient approach startTime = System.nanoTime(); HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>(); for (int i = 0; i < 1000000; i++) for (String a : sArr) { MutableInteger initValue = new MutableInteger(1); MutableInteger oldValue = efficientCounter.put(a, initValue); if (oldValue != null) { initValue.set(oldValue.get() + 1); } } endTime = System.nanoTime(); duration = endTime - startTime; 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次,争取每次对代码的改变都最小。需要注意的是,你可能无法做到在程序中做到上述改动,或者试验结果受影响较大,原因可能是垃圾回收期。
Naive: 201716122 Better Approach: 112259166 Efficient Approach: 93066471 Better Approach (without containsKey): 69578496 Better Approach (without containsKey, with AtomicInteger): 94313287 Better Approach (without containsKey, with int[]): 65877234改进的计数器(不使用containsKey()):
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):
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[]):
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数组的那个方法。原文地址:Efficient Counter in Java
相关文章推荐
- 关于如何用java实现一个高效的计数器
- 关于如何用java实现一个高效的计数器
- 在java中高效的计数器
- java字符串替换函数高效实现
- java实现高效的枚举元素集合示例
- Java web热部署 (Javarebel实现真正高效的tomcat热部署)
- 程序员面试金典——解题总结: 9.14Java 14.6实现CircularAray类,支持类似数组的数据结构,这些数据结构可以高效地进行旋转
- Java实现的一个高效的循环数据Buffer
- Java基础高效复习(hashmap的实现原理)
- 简单图片的计数器也可用java实现
- Java高效计数器
- 如何编写出高效的数据库连接池(附带完整代码C#和Java实现)
- java.io.BufferedReader 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
- Java实现-高效排序算法之基数排序
- Java web热部署 (Javarebel实现真正高效的tomcat热部署)
- JAVA Web快速开发部署(Javarebel实现真正高效的tomcat热部署)
- 黑马程序员_java高效计数器
- 实现高效易用的java操作mysql包装
- java实现word转pdf文件(高效不失真)
- 在Domino中使用Java系列实例1--在Lotus Domino中使用JavaMail实现高效外发邮件