本文转载地址:
             http://blog.csdn.net/renfufei/article/details/14120775

我们经常使用 HashMap作为计数器(counter)来统计数据库或者文本中的某些东西.
本文将使用HashMap来实现计数器的3种不同方式进行对比。

1. 新手级计数器
如果使用这一类别的计数器,那么代码大致如下所示:

  1. String source = "my name is name me and your name is her first her";
  2. String[] words = source.split(" ");
  3. // 新手级计数器
  4. public static void testNaive(String[] words){
  5. HashMap<String, Integer> counter = new HashMap<String, Integer>();
  6. for (String w : words) {
  7. if(counter.containsKey(w)){
  8. int oldValue = counter.get(w);
  9. counter.put(w, oldValue+1);
  10. } else {
  11. counter.put(w, 1);
  12. }
  13. }
  14. }

在每次循环中,判断是否包含了相应的key,如果包含,那么值在原来的基础上加1,如果没有,那就设置为1.
此种方式简单又直接,但并不是很有效率。效率不高的原因如下:
1.1 当一个key存在时,containsKey() 和 get() 分别调用了一次,这意味着对map进行了两次查找。
1.2 因为 Integer 是不可变的,每次循环在增加计数值的时候将会创建一个新的对象.

2. 入门级计数器
那么我们自然需要使用一个可变的整数来避免创建太多个Integer对象.可变整数类可以如下面所示来定义:

  1. // 可变Integer
  2. public static final class MutableInteger{
  3. private int val;
  4. public MutableInteger(int val){
  5. this.val = val;
  6. }
  7. public int get(){
  8. return this.val;
  9. }
  10. public void set(int val){
  11. this.val = val;
  12. }
  13. // 为了方便打印
  14. public String toString() {
  15. return Integer.toString(val);
  16. }
  17. }

那么计数器可以用如下的方式来改进:

  1. // 入门级计数器
  2. public static void testBetter(String[] words){
  3. HashMap<String, MutableInteger> counter = new HashMap<String, MutableInteger>();
  4. for (String w : words) {
  5. if(counter.containsKey(w)){
  6. MutableInteger oldValue = counter.get(w);
  7. oldValue.set(oldValue.get()+1); // 因为是引用,所以减少了一次HashMap查找
  8. } else {
  9. counter.put(w, new MutableInteger(1));
  10. }
  11. }
  12. }

因为不需要创建太多的Integer对象,看起来好了一些。然而,key存在的情况下,每次循环依然要进行两次查找.

3. 卓越级计数器
HashMap 的 put(key,value) 方法会返回key对应的当前value.了解这个特性,我们可以利用原有值来进行递增,并不需要多次的查找.

  1. public static void testEfficient(String[] words){
  2. HashMap<String, MutableInteger> counter = new HashMap<String, MutableInteger>();
  3. for (String w : words) {
  4. MutableInteger initValue = new MutableInteger(1);
  5. // 利用 HashMap 的put方法弹出旧值的特性
  6. MutableInteger oldValue = counter.put(w, initValue);
  7. if(oldValue != null){
  8. initValue.set(oldValue.get() + 1);
  9. }
  10. }
  11. }

4. 性能差异
为了测试这三种实现方式的性能,采用了下面的代码。先看看结果如何,性能测试分别执行了多次,对每一个数量级的测试,误差不算太大,所以取其中的一个结果排列如下:

  1. 10000000 次循环:
  2. 新手级计数器: 7726594902
  3. 入门级计数器: 6516014840
  4. 卓越级计数器: 5736574103
  5. 1000000 次循环:
  6. 新手级计数器: 777480106
  7. 入门级计数器: 642932000
  8. 卓越级计数器: 571867738
  9. 100000 次循环:
  10. 新手级计数器: 84323682
  11. 入门级计数器: 70176906
  12. 卓越级计数器: 61219664
  13. 10000 次循环:
  14. 新手级计数器: 13279550
  15. 入门级计数器: 7874100
  16. 卓越级计数器: 6460172
  17. 1000 次循环:
  18. 新手级计数器: 4542172
  19. 入门级计数器: 2933248
  20. 卓越级计数器: 992749
  21. 100 次循环:
  22. 新手级计数器: 3092325
  23. 入门级计数器: 1101695
  24. 卓越级计数器: 423942
  25. 10 次循环:
  26. 新手级计数器: 1993788
  27. 入门级计数器: 558150
  28. 卓越级计数器: 153156
  29. 1 次循环:
  30. 新手级计数器: 1625898
  31. 入门级计数器: 427494
  32. 卓越级计数器: 69473

从上面的输出可以看到,10000次的时候, 13:8:6 秒,相差很明显.特别是 新手级计数器和入门级计数器之间的比例,这说明创建对象是很耗资源的操作。
当然,次数更多的差距不明显的原因在于,触发了多次的GC垃圾回收,同时也证明了垃圾回收的代价确实很大。

完整的测试代码如下:

  1. import java.util.HashMap;
  2. public class TestCounter {
  3. public static void main(String[] args) {
  4. // 源字符串
  5. String source = "my name is name me and your name is her first her";
  6. // 计时,单位: 微秒
  7. long startTime = 0;
  8. long endTime = 0;
  9. long duration = 0;
  10. // 测试次数
  11. int loop = 1 * 10000;
  12. System.out.println(loop +" 次循环:");
  13. startTime = System.nanoTime();
  14. testNaive(source,loop);
  15. endTime = System.nanoTime();
  16. duration = endTime - startTime;
  17. System.out.println("新手级计数器: " + duration);
  18. //
  19. startTime = System.nanoTime();
  20. testBetter(source, loop);
  21. endTime = System.nanoTime();
  22. duration = endTime - startTime;
  23. System.out.println("入门级计数器: " + duration);
  24. //
  25. startTime = System.nanoTime();
  26. testEfficient(source, loop);
  27. endTime = System.nanoTime();
  28. duration = endTime - startTime;
  29. System.out.println("卓越级计数器: " + duration);
  30. }
  31. // 新手级计数器
  32. public static void testNaive(String source, int loop){
  33. if(null == source){
  34. return;
  35. }
  36. //
  37. String[] words = source.split(" ");
  38. for (int i = 0; i < loop; i++) {
  39. testNaive(words);
  40. }
  41. }
  42. public static void testNaive(String[] words){
  43. HashMap<String, Integer> counter = new HashMap<String, Integer>();
  44. for (String w : words) {
  45. if(counter.containsKey(w)){
  46. int oldValue = counter.get(w);
  47. counter.put(w, oldValue+1);
  48. } else {
  49. counter.put(w, 1);
  50. }
  51. }
  52. }
  53. // 可变Integer
  54. public static final class MutableInteger{
  55. private int val;
  56. public MutableInteger(int val){
  57. this.val = val;
  58. }
  59. public int get(){
  60. return this.val;
  61. }
  62. public void set(int val){
  63. this.val = val;
  64. }
  65. // 为了方便打印
  66. public String toString() {
  67. return Integer.toString(val);
  68. }
  69. }
  70. // 入门级计数器
  71. public static void testBetter(String source, int loop){
  72. if(null == source){
  73. return;
  74. }
  75. //
  76. String[] words = source.split(" ");
  77. for (int i = 0; i < loop; i++) {
  78. testBetter(words);
  79. }
  80. }
  81. public static void testBetter(String[] words){
  82. HashMap<String, MutableInteger> counter = new HashMap<String, MutableInteger>();
  83. for (String w : words) {
  84. if(counter.containsKey(w)){
  85. MutableInteger oldValue = counter.get(w);
  86. oldValue.set(oldValue.get()+1); // 因为是引用,所以减少了一次HashMap查找
  87. } else {
  88. counter.put(w, new MutableInteger(1));
  89. }
  90. }
  91. }
  92. // 卓越级计数器
  93. public static void testEfficient(String source, int loop){
  94. if(null == source){
  95. return;
  96. }
  97. //
  98. String[] words = source.split(" ");
  99. for (int i = 0; i < loop; i++) {
  100. testEfficient(words);
  101. }
  102. }
  103. public static void testEfficient(String[] words){
  104. HashMap<String, MutableInteger> counter = new HashMap<String, MutableInteger>();
  105. for (String w : words) {
  106. MutableInteger initValue = new MutableInteger(1);
  107. // 利用 HashMap 的put方法弹出旧值的特性
  108. MutableInteger oldValue = counter.put(w, initValue);
  109. if(oldValue != null){
  110. initValue.set(oldValue.get() + 1);
  111. }
  112. }
  113. }
  114. }

当你实用计数器的时候,很可能也需要根据值来进行排序的方法,请参考: 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 方法:):

  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. if (value != null) {
  6. value.set(value.get() + 1);
  7. }
  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. if (value != null) {
  6. value.incrementAndGet();
  7. }
  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. if (valueWrapper == null) {
  6. intCounter.put(a, new int[] { 1 });
  7. }
  8. else {
  9. valueWrapper[0]++;
  10. }
  11. }

Guava 语言的 MultiSet 可能更快一些.

6. 结论
优胜者是使用int数组的方式.

Java高效计数器的更多相关文章

  1. Java高效读取大文件

    1.概述 本教程将演示如何用Java高效地读取大文件.这篇文章是Baeldung (http://www.baeldung.com/) 上“Java——回归基础”系列教程的一部分. 2.在内存中读取 ...

  2. Java高效读取大文件(转)

    1.概述 本教程将演示如何用Java高效地读取大文件.这篇文章是Baeldung(http://www.baeldung.com/) 上“Java——回归基础”系列教程的一部分. 2.在内存中读取 读 ...

  3. java高效判断素数

    java高效判断素数 package solution; public class Prime { // 偶数可以由有两个素数相加得到, 一个偶数可能有多个这样的两个素数, 请寻找到 这样两个素数,让 ...

  4. Java高效编程:总结分享

    参考资料:慕课网:Java高效编程收费实战课程.博客园.CSDN.菜鸟教程以及其他文档. 篇幅受限,不太想针对每个点都写篇博客,有的地方可能写的不是很详细,一笔带过了.如果你觉得那个点在项目中用得上可 ...

  5. 剑指Java高效编程教程

    教程介绍 所谓"武以快为尊,天下武功唯快不破".本课程剑指Java高效编程,致力于从"技术"和"工具"两大 维度提高编程效率,帮助广大程序员 ...

  6. Java实现的高效计数器

    本文转载地址:            http://blog.csdn.net/snarlfuture/article/details/17049731 在统计来自数据库或文本中某些内容的频率时,你可 ...

  7. Java并发计数器探秘

    前言 一提到线程安全的并发计数器,AtomicLong 必然是第一个被联想到的工具.Atomic* 一系列的原子类以及它们背后的 CAS 无锁算法,常常是高性能,高并发的代名词.本文将会阐释,在并发场 ...

  8. Java高效编程之三【类和接口】

    本部分包含的一些指导原则,可以帮助哦我们更好滴利用这些语言元素,以便让设计出来的类更加有用.健壮和灵活. 十二.使类和成员的访问能力最小化 三个关键词访问修饰符:private(私有的=类级别的).未 ...

  9. Java 高效检查一个数组中是否包含某个值

    如何检查一个数组(未排序)中是否包含某个特定的值?在Java中,这是一个非常有用并又很常用的操作.同时,在StackOverflow中,有时一个得票非常高的问题.在得票比较高的几个回答中,时间复杂度差 ...

随机推荐

  1. 查看Samba用户的方法

    有时我们需要查看服务器上都注册了哪些用户,这时我们就可以用下面的命令来查看了. pdbedit可以编辑samba的用户数据库,具体使用方法可以用man查看. pdbedit -L

  2. URL转换成二维码

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6685804.html 二维码已经成为我们日常生活中的一个不可获取的产物,火车票上,景区门票,超市付款等等都 ...

  3. myeclipse2017破解失败解决办法

    最近,笔者安装的myeclipse2017破解出了问题,破解本来是很简单的事,就是几步而已,但是一直出问题,现在安利一波myeclipse2017版破解失败解决办法.诸如下图:()因为笔者已经破解好了 ...

  4. linux ssh免密码登录的原理

    免密码登录原理 图解,server A免登录到server B: 1.在A上生成公钥私钥. 2.将公钥拷贝给server B,要重命名成authorized_keys(从英文名就知道含义了) 3.Se ...

  5. 开源的C#实现WebSocket协议客户端和服务器websocket-sharp组件解析

    很久没有写博客了(至少自己感觉很长时间没有写了),没办法啊,楼主也是需要生活的人啊,这段一直都在找工作什么的.(整天催我代码的人,还望多多谅解啊,我会坚持写我们的项目的,还是需要相信我的,毕竟这是一个 ...

  6. java线程池ThreadPoolExector源码分析

    java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...

  7. asp.net core 编译mvc,routing,security源代码进行本地调试

    因为各种原因,需要查看asp.net core mvc的源代码来理解运行机制等等,虽说源代码查看已经能很好的理解了.但是能够直接调试还是最直观的.所有就有了本次尝试. 因调试设置源代码调试太辍笔,所以 ...

  8. Python标准模块--importlib

    作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python提供了importlib包作为标准库的一 ...

  9. lua 运算符

    lua 运算符 算术运算符 操作符 描述 + 加 - 减 * 乘 / 除 % 求模 ^ 求幂 示例程序 local a, b = 1, 2 print(a + b) print(a - b) prin ...

  10. [编织消息框架][网络IO模型]aio

    asynchronous I/O (the POSIX aio_functions)—————异步IO模型最大的特点是 完成后发回通知. [编织消息框架][网络IO模型]NIO(select and ...