Java实现的高效计数器
本文转载地址:
http://blog.csdn.net/snarlfuture/article/details/17049731
在统计来自数据库或文本中某些内容的频率时,你可能经常会用到HashMap。本文对比了三种用HashMap实现的计数器。
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数组的那个方法。
Java实现的高效计数器的更多相关文章
- 要学Java,怎么高效地学习,怎么规划
要学Java,怎么高效地学习,怎么规划? 题主是一个个例,99%的人(包括我自己)都没有题主这样的经历,也很难提出具有很强参考性的java学习建议.我倒是之前面试过一个跟题主有点类似的人,拿出来分 ...
- 一种从JSON数据创建Java类的高效办法
<一种从JSON数据创建Java类的高效办法> 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs JSON格式的数据经常会遇到,比如调用Web服 ...
- 在java中构建高效的结果缓存
文章目录 使用HashMap 使用ConcurrentHashMap FutureTask 在java中构建高效的结果缓存 缓存是现代应用服务器中非常常用的组件.除了第三方缓存以外,我们通常也需要在j ...
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- Java高效计数器
本文转载地址: http://blog.csdn.net/renfufei/article/details/14120775 我们经常使用 HashMap作为计数器(coun ...
- 161101、在Java中如何高效判断数组中是否包含某个元素
如何检查一个数组(无序)是否包含一个特定的值?这是一个在Java中经常用到的并且非常有用的操作.同时,这个问题在Stack Overflow中也是一个非常热门的问题.在投票比较高的几个答案中给出了几种 ...
- 在Java中如何高效的判断数组中是否包含某个元素
原文出处: hollischuang(@Hollis_Chuang) 如何检查一个数组(无序)是否包含一个特定的值?这是一个在Java中经常用到的并且非常有用的操作.同时,这个问题在Stack Ove ...
- java中如何高效的判断数组中是否包含某个元素---
package zaLearnpackage; import org.apache.commons.lang3.ArrayUtils; import java.util.Arrays; import ...
- java 实现新浪微博内容计数器 Java问题通用解决代码
http://www.mr3g.net/?p=220 参考sina的js版本而来,费弄最多的时间就是java对ansii码的判断了,js直接就是isascii()函数就可以实现了,java还要想办法 ...
随机推荐
- 手机自动化测试:appium源码分析之bootstrap十一
手机自动化测试:appium源码分析之bootstrap十一 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...
- Git安装与上传代码至Github
转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6642887.html 这篇文章应该是全网最新,最全,最靠谱的Github安装到上传代码的流程. 1.Git ...
- Vuex随笔
最近在项目中使用到了vuex,但是在配合vue使用时,也还是遇到了不少的问题,最终还是解决了问题,因此写一篇随笔来记录期间遇到的问题吧 项目概要: Vuex中所储存的的状态如下: Vue中:有一个ta ...
- linux常用脚本
转载于http://justcoding.iteye.com/blog/1943504 我们在运维中,尤其是linux运维,都知道脚本的重要性,脚本会让我们的 运维事半功倍,所以学会写脚本是我们每个l ...
- java小题:福尔摩斯的约会
原题地址:https://www.nowcoder.com/pat/6/problem/4040 防止广告嫌疑,原题为: 题目描述 大侦探福尔摩斯接到一张奇怪的字条:"我们约会吧! 3485 ...
- 关于constraint的用法
1.主键约束:要对一个列加主键约束的话,这列就必须要满足的条件就是非空因为主键约束:就是对一个列进行了约束,约束为(非空.不重复)以下是代码 要对一个列加主键,列名为id,表名为emp格式为:alt ...
- vue-router2 使用
VUE-ROUTER2 API http://router.vuejs.org/zh-cn/api/router-link.html 1,安装vue-router npm install vue ...
- clamav 杀毒软件安装及使用配置
安装clamav 之前还需要安装zlib 要不然安装过程中会报错的. tar -zxvf zlib-1.2.3.tar.gz cd zlib-1.2.3 ./configure make make ...
- redis 字典
redis 字典 前言 借鉴了 黄健宏 的 <<Redis 设计与实现>> 一书, 对 redis 源码进行学习 欢迎大家给予意见, 互相沟通学习 概述 字典是一种用于存储键值 ...
- 项目中如何使用babel6详解
由于浏览器的版本和兼容性问题,很多es6,es7的新的方法都不能使用,等到可以使用的时候,可能已经过去了很多年.Babel可以把es6,es7的新代码编译成兼容绝大多数的主流浏览器的代码. 本篇文章主 ...