2019独角兽企业重金招聘Python工程师标准>>>

HashMap是一个高效通用的数据结构,它在每一个Java程序中都随处可见。先来介绍些基础知识。你可能也知道,HashMap使用key的hashCode()和equals()方法来将值划分到不同的桶里。桶的数量通常要比map中的记录的数量要稍大,这样每个桶包括的值会比较少(最好是一个)。当通过key进行查找时,我们可以在常数时间内迅速定位到某个桶(使用hashCode()对桶的数量进行取模)以及要找的对象。

这些东西你应该都已经知道了。你可能还知道哈希碰撞会对hashMap的性能带来灾难性的影响。如果多个hashCode()的值落到同一个桶内的时候,这些值是存储到一个链表中的。最坏的情况下,所有的key都映射到同一个桶中,这样hashmap就退化成了一个链表——查找时间从O(1)到O(n)。我们先来测试下正常情况下hashmap在Java 7和Java 8中的表现。为了能完成控制hashCode()方法的行为,我们定义了如下的一个Key类:

class Key implements Comparable<Key> {private final int value;Key(int value) {this.value = value;}@Overridepublic int compareTo(Key o) {return Integer.compare(this.value, o.value);}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass())return false;Key key = (Key) o;return value == key.value;}@Overridepublic int hashCode() {return value;}}

Key类的实现中规中矩:它重写了equals()方法并且提供了一个还算过得去的hashCode()方法。为了避免过度的GC,我将不可变的Key对象缓存了起来,而不是每次都重新开始创建一遍:

class Key implements Comparable<Key> {public class Keys {public static final int MAX_KEY = 10_000_000;private static final Key[] KEYS_CACHE = new Key[MAX_KEY];static {for (int i = 0; i < MAX_KEY; ++i) {KEYS_CACHE[i] = new Key(i);}}public static Key of(int value) {return KEYS_CACHE[value];}}

现在我们可以开始进行测试了。我们的基准测试使用连续的Key值来创建了不同的大小的HashMap(10的乘方,从1到1百万)。在测试中我们还会使用key来进行查找,并测量不同大小的HashMap所花费的时间:

import com.google.caliper.Param;import com.google.caliper.Runner;import com.google.caliper.SimpleBenchmark;public class MapBenchmark extends SimpleBenchmark {private HashMap<Key, Integer> map;@Paramprivate int mapSize;@Overrideprotected void setUp() throws Exception {map = new HashMap<>(mapSize);for (int i = 0; i < mapSize; ++i) {map.put(Keys.of(i), i);}}public void timeMapGet(int reps) {for (int i = 0; i < reps; i++) {map.get(Keys.of(i % mapSize));}}}

有意思的是这个简单的HashMap.get()里面,Java 8比Java 7要快20%。整体的性能也相当不错:尽管HashMap里有一百万条记录,单个查询也只花了不到10纳秒,也就是大概我机器上的大概20个CPU周期。相当令人震撼!不过这并不是我们想要测量的目标。

假设有一个很差劲的key,他总是返回同一个值。这是最糟糕的场景了,这种情况完全就不应该使用HashMap:

class Key implements Comparable<Key> {//...@Overridepublic int hashCode() {return 0;}}

Java 7的结果是预料中的。随着HashMap的大小的增长,get()方法的开销也越来越大。由于所有的记录都在同一个桶里的超长链表内,平均查询一条记录就需要遍历一半的列表。因此从图上可以看到,它的时间复杂度是O(n)。

不过Java 8的表现要好许多!它是一个log的曲线,因此它的性能要好上好几个数量级。尽管有严重的哈希碰撞,已是最坏的情况了,但这个同样的基准测试在JDK8中的时间复杂度是O(logn)。单独来看JDK 8的曲线的话会更清楚,这是一个对数线性分布:

为什么会有这么大的性能提升,尽管这里用的是大O符号(大O描述的是渐近上界)?其实这个优化在JEP-180中已经提到了。如果某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD = 8),HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好,是O(logn),而不是糟糕的O(n)。它是如何工作的?前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面,这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树,使用哈希值作为树的分支变量,如果两个哈希值不等,但指向同一个桶的话,较大的那个会插入到右子树里。如果哈希值相等,HashMap希望key值最好是实现了Comparable接口的,这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的,不过如果实现了当然最好。如果没有实现这个接口,在出现严重的哈希碰撞的时候,你就并别指望能获得性能提升了。

这个性能提升有什么用处?比方说恶意的程序,如果它知道我们用的是哈希算法,它可能会发送大量的请求,导致产生严重的哈希碰撞。然后不停的访问这些key就能显著的影响服务器的性能,这样就形成了一次拒绝服务攻击(DoS)。JDK 8中从O(n)到O(logn)的飞跃,可以有效地防止类似的攻击,同时也让HashMap性能的可预测性稍微增强了一些。我希望这个提升能最终说服你的老大同意升级到JDK 8来。

测试使用的环境是:Intel Core i7-3635QM @ 2.4 GHz,8GB内存,SSD硬盘,使用默认的JVM参数,运行在64位的Windows 8.1系统 上。

转载于:https://my.oschina.net/u/1474079/blog/280819

java8: hashmap性能提升相关推荐

  1. jdk1.8 HashMap性能提升

    原文链接:http://it.deepinmind.com/%E6%80%A7%E8%83%BD/2014/04/24/hashmap-performance-in-java-8.html HashM ...

  2. java8hashmap_Java 8中的HashMap性能改进

    java8hashmap HashMap<K, V>是每个Java程序中快速,通用且无处不在的数据结构. 首先是一些基础知识. 您可能知道,它使用键的hashCode()和equals() ...

  3. Java 8中的HashMap性能改进

    HashMap<K, V>是每个Java程序中快速,通用且无处不在的数据结构. 首先是一些基础知识. 您可能知道,它使用键的hashCode()和equals()方法在存储桶之间拆分值. ...

  4. switch 的性能提升了 3 倍,我只用了这一招!

    这是我的第 190 期分享 作者 | 王磊 来源 | Java中文社群(ID:javacn666) 分享 | Java中文社群(ID:javacn666) 上一篇<if快还是switch快?解密 ...

  5. Web的现状:网页性能提升指南

    互联网发展非常迅速,所以我们创造了Web平台.通常 我们会忽视连通性等问题,但用户们却不会视而不见 .一瞥万维网的现状,可以发现我们并没有用同情心.变通意识去构建它,更不要说性能了. 所以,今天的We ...

  6. 大叔也说并行和串行`性能提升N倍(N由操作系统位数和cpu核数决定)

    并行是.net4.5主打的技术,同时被封装到了System.Threading.Tasks命名空间下,对外提供了静态类Parallel,我们可以直接使用它的静态方法,它可以并行一个委托数组,或者一个I ...

  7. 深度干货!如何将深度学习训练性能提升数倍?

    作者 | 车漾,阿里云高级技术专家 顾荣,南京大学副研究员 责编 | 唐小引 头图 | CSDN 下载自东方 IC 出品 | CSDN(ID:CSDNnews) 近些年,以深度学习为代表的人工智能技术 ...

  8. 性能提升3倍的树莓派4,被爆设计缺陷!

    整理 | 屠敏 转载自CSDN(ID:CSDNnews) 一直以来,素有世界最小电脑之称的 Raspberry Pi(树莓派)是一种独特的存在.它不仅只有一块信用卡般的体积,还具备主机电脑所具备的功能 ...

  9. AAAI 2020 | 滴滴东北大学提出自动结构化剪枝压缩算法框架,性能提升高达120倍...

    点击上方"视学算法",选择"星标" 快速获得最新干货 2020 年 2 月 7 日-2 月 12 日,AAAI 2020 将于美国纽约举办.不久之前,大会官方公 ...

最新文章

  1. 基于Flutter+FaaS的业务框架思考与实践
  2. will not add file alias already exists in index(git上传代码出错)
  3. 中国邮政编码.html,全国邮政编码一览表.doc
  4. android 省市区选择器
  5. 《无线网络:理解和应对互联网环境下网络互连所带来的挑战》——第2章 无线生态系统 2.1无线标准化过程...
  6. 如何利用openSsl来计算一个文件的md5值?
  7. 随想录(构建自己的代码库)
  8. 农村70后、80后、90后的儿时玩具有哪些?
  9. 浏览器显示html过程,浏览器显示页面的流程
  10. 高通IPQ5018,QSDK V11.5版本手动编译指南-64位
  11. 离散数学-7 二元关系
  12. 设计模式学习——代理模式(proxy)
  13. 事件查看器mysql_Windows 事件查看器(Event Viewer) 检查日志的方法
  14. 关于以太网没有有效的ip配置问题解决方法
  15. 就这?阿里p6的面试竟然这么简单
  16. Metasploit(一) 利用 MS17-010 漏洞进行攻击
  17. SAP_什么是BAPI
  18. html中实现图片的无限滚动,CSS3 背景图片无限滚动之波浪效果的实现
  19. 关于oracle导入数据出现字符集问题及一点点解决办法!
  20. 权限管理系统(包括审批流程)数据库设计图

热门文章

  1. 修改鼠标手形 闪烁 在填写文字内容后也一直在闪烁
  2. pandas rolling方法_Pandas处理时间序列数据的20个关键知识点
  3. 计算机培训营,计算机学院举办科创训练营第三期培训
  4. java8 方法引用好处_Java 8方法引用
  5. AES方式加解密的简单介绍
  6. latex sign_LATEX科研论文写作教程
  7. 把计算机怎么连接手机的网络助手在哪里,手机连接电脑上网,详细教您手机如何连接电脑上网...
  8. 帆软参数设置_系统参数设置- FineReport帮助文档|报表开发|报表使用|学习教程
  9. python字典forward_《Python机器学习基础教程》
  10. Oracle 12c中导入Oracle 11g的数据