本文转载地址:

           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. //naive approach
  4. HashMap<String, Integer> counter = new HashMap<String, Integer>();
  5. for(String a : sArr) {
  6. if(counter.containsKey(a)) {
  7. int oldValue = counter.get(a);
  8. counter.put(a, oldValue+1);
  9. } else {
  10. counter.put(a, 1);
  11. }
  12. }

每次循环,你都要判断键(key)是否存在。如果该键存在,你需要键对应的值加1,否则,这设置对应的值为1。该方法看起来简单而直接,但它并不是最有效率的方法,它在如下方面欠考虑:

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

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

2. 改进的计数器

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

  1. class MutableInteger {
  2. private int val;
  3. public MutableInteger(int val) {
  4. this.val = val;
  5. }
  6. public int get() {
  7. return val;
  8. }
  9. public void set(int val) {
  10. this.val = val;
  11. }
  12. //used to print value convinently
  13. public String toString() {
  14. return Integer.toString(val);
  15. }
  16. }

改进后的计数器如下所示:

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

改进后的计数器无需创建大量的整数(Integer)对象,效率有所提高,但是它还有没有解决的问题:当键(key)存在时需要搜索两次map。

3. 高效的计数器

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

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

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. long startTime = 0;
  4. long endTime = 0;
  5. long duration = 0;
  6. // naive approach
  7. startTime = System.nanoTime();
  8. HashMap<String, Integer> counter = new HashMap<String, Integer>();
  9. for (int i = 0; i < 1000000; i++)
  10. for (String a : sArr) {
  11. if (counter.containsKey(a)) {
  12. int oldValue = counter.get(a);
  13. counter.put(a, oldValue + 1);
  14. } else {
  15. counter.put(a, 1);
  16. }
  17. }
  18. endTime = System.nanoTime();
  19. duration = endTime - startTime;
  20. System.out.println("Naive Approach :  " + duration);
  21. // better approach
  22. startTime = System.nanoTime();
  23. HashMap<String, MutableInteger> newCounter = new HashMap<String, MutableInteger>();
  24. for (int i = 0; i < 1000000; i++)
  25. for (String a : sArr) {
  26. if (newCounter.containsKey(a)) {
  27. MutableInteger oldValue = newCounter.get(a);
  28. oldValue.set(oldValue.get() + 1);
  29. } else {
  30. newCounter.put(a, new MutableInteger(1));
  31. }
  32. }
  33. endTime = System.nanoTime();
  34. duration = endTime - startTime;
  35. System.out.println("Better Approach:  " + duration);
  36. // efficient approach
  37. startTime = System.nanoTime();
  38. HashMap<String, MutableInteger> efficientCounter = new HashMap<String, MutableInteger>();
  39. for (int i = 0; i < 1000000; i++)
  40. for (String a : sArr) {
  41. MutableInteger initValue = new MutableInteger(1);
  42. MutableInteger oldValue = efficientCounter.put(a, initValue);
  43. if (oldValue != null) {
  44. initValue.set(oldValue.get() + 1);
  45. }
  46. }
  47. endTime = System.nanoTime();
  48. duration = endTime - startTime;
  49. 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. if (value != null) {
  6. value.set(value.get() + 1);
  7. } else {
  8. efficientCounter2.put(a, new MutableInteger(1));
  9. }
  10. }

改进的计数器(不使用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. } else {
  8. atomicCounter.put(a, new AtomicInteger(1));
  9. }
  10. }

改进的计数器(不使用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. } else {
  8. valueWrapper[0]++;
  9. }
  10. }

Guava的MultiSet可能更快。

6. 总结

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

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

  1. 要学Java,怎么高效地学习,怎么规划

    要学Java,怎么高效地学习,怎么规划?   题主是一个个例,99%的人(包括我自己)都没有题主这样的经历,也很难提出具有很强参考性的java学习建议.我倒是之前面试过一个跟题主有点类似的人,拿出来分 ...

  2. 一种从JSON数据创建Java类的高效办法

    <一种从JSON数据创建Java类的高效办法> 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs JSON格式的数据经常会遇到,比如调用Web服 ...

  3. 在java中构建高效的结果缓存

    文章目录 使用HashMap 使用ConcurrentHashMap FutureTask 在java中构建高效的结果缓存 缓存是现代应用服务器中非常常用的组件.除了第三方缓存以外,我们通常也需要在j ...

  4. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

  5. Java高效计数器

    本文转载地址:              http://blog.csdn.net/renfufei/article/details/14120775 我们经常使用 HashMap作为计数器(coun ...

  6. 161101、在Java中如何高效判断数组中是否包含某个元素

    如何检查一个数组(无序)是否包含一个特定的值?这是一个在Java中经常用到的并且非常有用的操作.同时,这个问题在Stack Overflow中也是一个非常热门的问题.在投票比较高的几个答案中给出了几种 ...

  7. 在Java中如何高效的判断数组中是否包含某个元素

    原文出处: hollischuang(@Hollis_Chuang) 如何检查一个数组(无序)是否包含一个特定的值?这是一个在Java中经常用到的并且非常有用的操作.同时,这个问题在Stack Ove ...

  8. java中如何高效的判断数组中是否包含某个元素---

    package zaLearnpackage; import org.apache.commons.lang3.ArrayUtils; import java.util.Arrays; import ...

  9. java 实现新浪微博内容计数器 Java问题通用解决代码

    http://www.mr3g.net/?p=220 参考sina的js版本而来,费弄最多的时间就是java对ansii码的判断了,js直接就是isascii()函数就可以实现了,java还要想办法 ...

随机推荐

  1. 各位Coder看过来

    为了丰富博客内容,也为了解决一些实际的问题,现准备出一系列博文,内容为各位回复评论指明需要的知识点,将在近期为你解决并提供还算精要的讲解:评论内容要求 Coder:+需要的技术内容.技术内容不限领域, ...

  2. android开发之-数据存储Map、HashMap、Hashtable、concurenthashmap区别

    选择一个map进行软件基础数据初始化操作,要求第一次初始化后,不修改数据,可能会出现静态类被回收,然后在进行初始化操作? 1.Map :接口 /** * A {@code Map} is a data ...

  3. sleep()和wait()的区别 --- 快入睡了,突然想起一个知识点,搞完就睡

    自从开了博客之后,大部分是转发的,不断的ctrl+c和ctrl+v,知识是越屯越多,吸收的却很少,后来越来越懒,直接保存链接了. 我已经认识到了自己的错误,自己查询到的这些知识,以后还是会定期保存的, ...

  4. ViewPager—02图片无限轮播

    布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:t ...

  5. 微信小程序省市联动

    最近呢刚好做了一个省市联动的功能,今天看到有人问这个怎么做,我就把我做的放上来共享一下: 首先呢,来看看效果,点击文字'点击',弹出选择窗口,点击取消或者确定(取消.确定按钮在选择框上边,截图有些不清 ...

  6. python作业设计:输入用户名密码,认证成功后显示欢迎信息,输错三次后锁定

    作业需求: 1.输入用户名密码 2.认证成功后显示欢迎信息 3.输错三次后锁定实现思路: 1.判断用户是否在黑名单,如果在黑名单提示账号锁定. 2.判断用户是否存在,如果不存在提示账号不存在. 3.判 ...

  7. Linux 入门笔记

    一开始对linux总有些抵触,黑黑的命令框不知道如何下手,这次因为工作交接的缘故需要负责之前同事的Node后端部分,node,redis这些都是部署在Linux上的,看了几次运维的同学噼里啪啦的敲命令 ...

  8. iOS项目中常见的文件

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  9. iterable

    iterable 阅读: 148111 遍历Array可以采用下标循环,遍历Map和Set就无法使用下标.为了统一集合类型,ES6标准引入了新的iterable类型,Array.Map和Set都属于i ...

  10. JS&Jquery中的遍历

    JavaScript中的遍历: 1.for 遍历 var anArray = ['one','two']; for(var n = 0; n < anArray.length; n++) {   ...