HashMap<K, V>是每个Java程序中快速,通用且无处不在的数据结构。 首先是一些基础知识。 您可能知道,它使用键的hashCode()equals()方法在存储桶之间拆分值。 存储桶(箱)的数量应略高于映射中的条目数,以便每个存储桶仅保留很少(最好是一个)值。 当按键查找时,我们很快确定了存储桶(使用hashCode()模数number_of_buckets模),并且我们的商品在固定时间可用。

这应该已经为您所了解。 您可能还知道,哈希冲突对HashMap性能具有灾难性的影响。 当多个hashCode()值最终出现在同一存储桶中时,这些值将放置在临时链接列表中。 在最坏的情况下,当所有键都映射到同一存储桶时,会将哈希映射退化为链表–从O(1)到O(n)查找时间。 让我们首先对HashMap在Java 7(1.7.0_40)和Java 8(1.8.0-b132)中的正常情况下的行为进行基准测试。 为了完全控制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实例,而不是一遍又一遍地创建它们:

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];}}

现在我们准备进行一些实验。 我们的基准测试将使用连续键空间简单地创建不同大小(10的幂,从1到1百万)的HashMap 。 在基准测试本身中,我们将根据键查找值并测量所需的时间,具体取决于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()确实是O(1):

有趣的是,在简单的HashMap.get() Java 8平均比Java 7快20%。 整体性能同样令人感兴趣:即使在HashMap有100万个条目,一次查找所用的时间也不到10纳秒,这意味着我的机器上大约有20个CPU周期* 。 令人印象深刻! 但这不是我们要进行基准测试的结果。

假设我们有一个非常差的映射键,它总是返回相同的值。 这是最糟糕的情况,完全HashMap使用HashMap

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

我使用了完全相同的基准来查看它在各种地图尺寸下的行为(注意这是对数对数比例):

预计Java 7的结果。 HashMap.get()的成本与HashMap本身的大小成比例地增长。 由于所有条目都在一个巨大的链接列表中的同一存储桶中,因此查找一个条目平均需要遍历该列表的一半(大小为n)。 因此,O(n)复杂度如图所示。

但是Java 8的性能要好得多! 这是一个对数标度,因此我们实际上在谈论几个数量级的更好。 在灾难性哈希冲突的情况下,在JDK 8上执行的相同基准会产生O(logn)最坏情况的性能,如将JDK 8单独以对数线性比例可视化,则可以更好地看到:

即使使用big-O表示法,如此巨大的性能改进背后的原因是什么? 好,在JEP-180中描述了此优化。 基本上,当存储桶过大时(当前: TREEIFY_THRESHOLD = 8 ), HashMap用树形图的临时实现动态替换它。 这样一来,我们不必感到悲观的O(n),而获得更好的O(logn)。 它是如何工作的? 好吧,以前具有冲突键的条目只是简单地附加到链表中,而后又需要遍历。 现在, HashMap使用哈希码作为分支变量,将列表提升为二叉树。 如果两个散列不同,但最终在同一个存储桶中,则认为一个散列较大并向右移动。 如果哈希值相等(如本例所示),则HashMap希望键是Comparable ,以便它可以建立一些顺序。 这不是HashMap密钥的要求,但显然是一种好习惯。 如果密钥不具有可比性,那么在发生大量哈希冲突的情况下,不要指望任何性能提高。

为什么所有这些都那么重要? 知道我们使用的哈希算法的恶意软件可能会处理数千个请求,这些请求将导致大量的哈希冲突。 重复访问此类密钥将严重影响服务器性能,从而有效地导致拒绝服务攻击。 在JDK 8中,从O(n)到O(logn)的惊人跳跃将阻止这种攻击媒介,也使性能更具预测性。 我希望这将最终说服您的老板升级。


*在Intel Core i7-3635QM @ 2.4 GHz,8 GiB RAM和SSD驱动器上执行的基准,在64位Windows 8.1和默认JVM设置上运行。

翻译自: https://www.javacodegeeks.com/2014/04/hashmap-performance-improvements-in-java-8.html

Java 8中的HashMap性能改进相关推荐

  1. java8hashmap_Java 8中的HashMap性能改进

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

  2. Java 8 中的 HashMap

    红黑树(red black tree) 特点 一个节点是红色或黑色 根节点是黑色 如果一个节点是红色,那么它的子节点必须是黑色 一个节点到一个null引用的每一条路径必须包含相同数目的黑色节点(红色节 ...

  3. 三十六、Java集合中的HashMap

    @Author:Runsen @Date:2020/6/3 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

  4. Java 8中HotSpot选项的改进文档

    Oracle的 Java 8 的HotSpot实现中引入的一些小但受欢迎的功能之一是在启动器的文档中添加了许多常见的HotSpot Java启动器 ( java )选项/标志. 过去,即使是对某些相当 ...

  5. Java 集合中的方法性能分析

    文章目录 前言 一.List集合 1.1.Collection 中 get() 和 remove()方法的效率 示例一 示例二 总结 前言 暂无 一.List集合 1.1.Collection 中 g ...

  6. arm为什么不支持java_为什么在Java 7中没有为ARM改进StAX类

    如果你仔细观察 close() method of AutoCloseable: Closes this resource, relinquishing any underlying resource ...

  7. 垃圾收集器–串行,并行,CMS,G1(以及Java 8中的新增功能)

    4个Java垃圾收集器–错误的选择如何严重影响性能 在2014年,对于大多数开发人员来说,还有两件事仍然是个谜:垃圾收集和了解异性. 由于我对后者知之甚少,所以我认为我会对前者大吃一惊,特别是因为在该 ...

  8. Java 8 中 Map 骚操作之 merge() 的用法

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | juejin.im/post/5d9b455a ...

  9. 后端:Java 8 中的 Map 实用操作,学习下!

    merge() 怎么用? merge() 简介 使用场景 其他 总结 Java 8最大的特性无异于更多地面向函数,有时约会了lambda等,可以更好地进行函数式编程. 前段时间无意间发现了map.me ...

最新文章

  1. Halcon知识 : 乘法图像融合
  2. 涂鸦智能 dubbo-go 亿级流量的实践与探索
  3. 条理清晰的搭建SSH环境
  4. 电脑边充电边用好吗_平板电脑充电推车厂家哪家好?
  5. IDEA忽略不必要提交的文件
  6. 让Mysql支持Emoji表情,解决[Err] 1366 - Incorrect string value: '\xF0\xA3\x84\x83'
  7. 蓝桥杯 ALGO-82 算法训练 输出米字形
  8. C#基础笔记(第十天)
  9. Python基础_列表与元组
  10. POJ 2391 Ombrophobic Bovines【二分+最大流】
  11. 为什么梯度反方向是函数下降最快的方向?
  12. 【计算机毕业设计】疫情社区管理系统的设计与实现
  13. html5手机的注册页面,H5页面结合vue实现登录注册组件
  14. 小程序验证身份证号、验证手机号方法
  15. 中秋节到了我给大家用python做一个月饼
  16. Ubuntu12.04 Skype4.2 提示Skype can't connect,安装Skype4.3
  17. android 5.0 pie,Android各版本份额占比出炉:Android Pie仍未知
  18. FileReader与FileWriter使用一例
  19. 学生喂养动物(猫,狗,鸟)
  20. java三个技术平台_java三大技术平台是什么

热门文章

  1. 2018蓝桥杯省赛---java---C---4( 第几个幸运数)
  2. 学生上课睡觉班主任怎么处理_【师问师答】学生上课说话,点名批评还嘴怎么办?...
  3. sql server累计求和函数_SQL基础--SQL高级功能
  4. azure 部署java_jClarity:在Azure上升级到Java
  5. rails jquery_Spring与Rails的jQuery UJS
  6. apache ignite_Apache Ignite变得简单:第一个Java应用程序
  7. 对称密钥加密算法 对称轮数_选择Java加密算法第2部分–单密钥对称加密
  8. tdd java_适用于Idea的面向现代TDD的Java 8 JUnit测试模板(带有Mockito和AssertJ)
  9. java message_Java Message System简介
  10. instanceof_您真的需要instanceof吗?