概述

JDK1.8中的HashMap较于前代有了较大的变更,主要变化在于扩容机制的改变。在JDK1.7及之前HashMap在扩容进行数组拷贝的时候采用的是头插法,因此会造成并发情景下形成环状链表造成死循环的问题。JDK1.8中改用了尾插法进行数组拷贝,修复了这个问题。

其次,JDK1.8开始HashMap改用数组+链表/红黑树组合的数据结构来提高查询效率,降低哈希冲突产生的链表过长导致的查询效率减缓现象。

本文的主要内容是对JDK1.8中的扩容机制与前代进行比较。

JDK1.8之前的扩容

由resize()和transfer()两个方法共同完成。

 /*** Rehashes the contents of this map into a new array with a* larger capacity.  This method is called automatically when the* number of keys in this map reaches its threshold.** If current capacity is MAXIMUM_CAPACITY, this method does not* resize the map, but sets threshold to Integer.MAX_VALUE.* This has the effect of preventing future calls.** @param newCapacity the new capacity, MUST be a power of two;*        must be greater than current capacity unless current*        capacity is MAXIMUM_CAPACITY (in which case value*        is irrelevant).*/void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}// 新建数组Entry[] newTable = new Entry[newCapacity];// 拷贝原数据transfer(newTable);// 得到新数组table = newTable;// 更改阈值threshold = (int)(newCapacity * loadFactor);}
    /*** Transfers all entries from current table to newTable.*/void transfer(Entry[] newTable) {Entry[] src = table;int newCapacity = newTable.length;for (int j = 0; j < src.length; j++) {// 获取当前node节点 eEntry<K,V> e = src[j];if (e != null) {// 将原数组该位置置空src[j] = null;do {// 获取下一元素Entry<K,V> next = e.next;// 计算元素在新数组中的索引int i = indexFor(e.hash, newCapacity);// 头插法 插入新数组中e.next = newTable[i];// 在新索引位置放入元素newTable[i] = e;// e 指向下一元素e = next;} while (e != null); //直至e为空,即全部复制完毕}}}

线程不安全问题

拷贝原数据采用的是头插法,在并发场景下,如果两个值在新数组中哈希冲突一样会出现环状链表的情形,最终导致死循环。如下图所示:

JDK1.8中的扩容

JDK1.8中将transfer()方法的操作也放入了resize()方法中,而由于JDK1.8引入了红黑树的结构,扩容的操作看起来也更加复杂。

 /*** Initializes or doubles table size.  If null, allocates in* accord with initial capacity target held in field threshold.* Otherwise, because we are using power-of-two expansion, the* elements from each bin must either stay at same index, or move* with a power of two offset in the new table.** @return the table*/final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaultsnewCap = DEFAULT_INITIAL_CAPACITY;newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);}if (newThr == 0) {float ft = (float)newCap * loadFactor;newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?(int)ft : Integer.MAX_VALUE);}threshold = newThr;// 新建数组@SuppressWarnings({"rawtypes","unchecked"})Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;if (oldTab != null) {// 数据转移操作for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;// 元素没有后续节点,直接放入新数组对应索引位置if (e.next == null)newTab[e.hash & (newCap - 1)] = e;// 元素是树节点,进行树转移操作(本文暂不考虑)else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order// 不是树节点并且有后续节点那就只剩下链表形式了Node<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;// 尾插法转移链表数据do {next = e.next;// 索引不进行变化,放入新数组和原数组一样的位置if ((e.hash & oldCap) == 0) {if (loTail == null)// 直接放入loHead = e;else// 尾插法loTail.next = e;loTail = e;}else {// 需要重新计算元素在新数组中的位置if (hiTail == null)// 直接放入hiHead = e;else// 尾插法hiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {// 重新计算的数组索引位置也就是原索引加上原数组长度hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}}return newTab;}

这里对 if ((e.hash & oldCap) == 0) 进行一个解释说明

  • 这个结果等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置,记为低位区链表lo开头-low;
  • 不等于0时,则将该头节点放到新数组时的索引位置等于其在旧数组时的索引位置再加上旧数组长度,记为高位区链表hi开头high.

具体推演过程见

(2条消息) HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导_Dylanu的博客-CSDN博客_e.hashhttps://blog.csdn.net/u010425839/article/details/106620440

jdk1.8 HashMap扩容机制变化相关推荐

  1. 七、JDK1.7中HashMap扩容机制

    导读 前面文章一.深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二.Jdk1.7和1.8中HashMap数据结构及源码分析 .三.JDK1.7和1.8Hash ...

  2. 八、JDK1.8中HashMap扩容机制

    导读 前面文章一.深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二.Jdk1.7和1.8中HashMap数据结构及源码分析 .三.JDK1.7和1.8Hash ...

  3. 必知必会--HashMap扩容机制

    前言 HashMap作为Java中使用最频繁的数据结构之一,它的技术原理与细节在面试中经常会被问到.笔者在面试美团时曾被面试官问到HashMap扩容机制的原理.这个问题倒不难,但是有些细节仍需注意. ...

  4. 聊一聊不同技术栈中hashmap扩容机制

    前言 hash简介 作为后端开发,说HashMap是我们最经常接触到的数据结构都不为过,而HashMap如其名最主要依赖的算法就是hash散列算法来存储和读取数据.         以关键码值K为自变 ...

  5. HashMap扩容机制(JDK1.8)-- 源码鉴赏与启发

    目录 一.几个重要的变量 二.HashMap扩容方法resize()分析 三.启发 一.几个重要的变量 1.默认初始化容量:     static final int DEFAULT_INITIAL_ ...

  6. hashmap扩容机制_图文并茂:HashMap经典详解!

    点击上方 Java后端,选择 设为星标 优质文章,及时送达 代码中的注解多看几遍,其中HashMap的扩容机制是要必懂知识!结合图片一起理解! 什么是 HashMap? HashMap 是基于哈希表的 ...

  7. hashmap扩容机制_图文并茂,HashMap经典详解!

    Java面试笔试面经.Java技术每天学习一点 公众号Java面试 关注我不迷路 作者:feigeswjtu 来源:https://github.com/feigeswjtu/java-basics ...

  8. JDK1.8 HashMap扩容

    说在前边 JDK1.8中的HashMap较于前代有了较大的变更,主要变化在于扩容机制的改变.在JDK1.7及之前HashMap在扩容进行数组拷贝的时候采用的是头插法,因此会造成并发情景下形成环状链表造 ...

  9. Java8 HashMap 扩容机制与线程安全分析

    如果大家有仔细阅读过 HashMap 的源码就会发现 HashMap 的哈希表初始化并不是在其构造函数中进行的,而是 resize() 方法. 这篇文章不对 HashMap 中的树进行介绍. 一.Ha ...

最新文章

  1. 在CentOS 6.3/6.5 64bit上为python 2.7.10安装pycurl模块
  2. ceph 集群 健康状态 监管
  3. MySQL 规范及优化
  4. mysql避免回环复制_【20181204】 MySQL 双主复制是如何避免回环复制的
  5. 动态so库的链接:运行时链接和编译时链接
  6. 服务器和交换机之间网络协议,网络协议是计算机网络中服务器,计算机,交换机.doc...
  7. cheatengine找不到数值_彩票中奖500万,领了还不到一半?这些问题不解决,钱都拿不走...
  8. call() apply() bind()
  9. C main()参数
  10. Solaris 10 ftp,telnet,ssh,sendmail
  11. python+tensorflow+captcha库:基于TF快速破解验证码
  12. Pickle Finance发起两项新提案欲增聘开发者并永久保留0xkoffee的任命
  13. C# 操作World生成报告
  14. 新建的邮件服务器只能发件不能收件_49. Django 2.1.7 使用内置函数发送邮件
  15. 无废话C#设计模式系列文章
  16. 20150201推荐
  17. OS + Unix FreeBSD / MacOSX Snow Leopard 10.6.3-8 / MacBook Pro / apple / MC373
  18. 升级ios16后iphone无法识别SIM?一招解决这个问题!
  19. 基于MQTT协议的WZ指令开发V3.0版本支持onenet
  20. 【转】黑客文化的精髓

热门文章

  1. 华为荣耀20和x10比较_华为畅享20和荣耀x10区别:功能与体验大大不同
  2. js判断对象是否为空
  3. 微信小程序上传图片报错:uploadFile:fail url not in domain list
  4. MySQL的存储引擎
  5. 计算机一级ms office常用函数公式,2017年计算机一级MS Office常见考点
  6. 蓝桥杯-最大比例-python
  7. web前端基础html,css,js,jquery
  8. 基于Spring Boot+vue的停车场管理系统 idea mysql
  9. 设计师:设计师知识储备之室内设计风格图文介绍大全(欧式-巴洛克风格+洛可可风格+法式风格、新古典风格、地中海风格、美式风格、日本和式风格 、东南亚风格、混搭风格)之详细攻略
  10. 【小5聊】Html基础之通过for循环和if相结合的方式实现1万内的完全平方数