1. resize定义

当HashMap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

2. 扩容触发条件

那么HashMap什么时候进行扩容呢?HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容。

loadFactor的默认值为0.75,数组大小为16。也就是说,默认情况下,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。

而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,HashMap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.75*1000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。

为什么数组默认长度是16?那是为了实现均匀分布。因为在使用2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。

3. 扩容原理

HashMap扩容分为两步:

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

为什么要重新Hash呢,不直接复制过去呢?因为长度扩大以后,Hash的规则也随之改变。

Hash的公式---> index = HashCode(Key) & (Length - 1)

原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了,之前的所有数据的hash值得到的位置都需要变化。

扩容前:

扩容后:

4. 头插法和尾插法

当HashMap要在链表里插入新的Entry时,在Java 8之前是将Entry插入到链表头部,在Java 8开始是插入链表尾部(Java 8用Node对象替代了Entry对象)。

Java 7插入链表头部,是考虑到新插入的数据,更可能作为热点数据被使用,放在头部可以减少查找时间。

Java 8改为插入链表尾部,原因就是防止环化。因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

这也是HashMap不能用于多线程场景的一个重要原因。我们来看一下多线程场景下的示例:

图是网上找的,话我就凑合着说了,图上的hash算法是自定义的,不要纠结这个,是简单的用key mod 一下表的大小(也就是数组的长度)。不是那个实际的hash算法!

最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。接下来的三个步骤是Hash表 扩容变成4,然后所有的

do {Entry<K,V> next = e.next; // <--假设线程一执行到这里就被调度挂起了int i = indexFor(e.hash, newCapacity);e.next = newTable[i];newTable[i] = e;e = next;
} while (e != null);

而我们的线程二执行完成了。于是我们有下面的这个样子。

注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。

这里的意思是线程1这会还没有完全开始扩容,但e和next已经指向了,线程2是正常的扩容的,那这会在3这个位置上,就是7->3这个顺序。

然后:线程一被调度回来执行。

回到线程1里面的时候,一切安好。线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。

这时候,原来的线程2里面的key7的e和key3的next没了,e=key3,next=null。当继续执行,需要将key3加回到key7的前面。
e.next = newTable[i] 导致 key(3).next 指向了 key(7)

注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

我理解是线程2生成的e和next的关系影响到了线程1里面的情况。从而打乱了正常的e和next的链。于是,当我们的线程一调用到,HashTable.get(11)时,即又到了3这个位置,需要插入新的,那这会就e 和next就乱了。

HashMap扩容机制以及尾插法相关推荐

  1. HashMap面试题 头插法、尾插法、hash冲突、数组扩容、ConcurrentHashMap

    文章目录 HashMap 的数据结构? HashMap 的工作原理? HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题? 数组扩容的 ...

  2. Hashmap扩容时出现循环链表(jdk1.8把头插法换成了尾插法的原因)

    参考:https://blog.csdn.net/sinat_39410753/article/details/106242573 1.容量计算 容量的阈值=容量*加载因子 2.扩容容量 扩容的容量大 ...

  3. hashmap头插法和尾插法区别

    前言 HashMap 应该算是 Java 后端工程师面试的必问题,因为其中的知识点太多,很适合用来考察面试者的 Java 基础. 开场 面试官: 你先自我介绍一下吧! 安琪拉: 我是安琪拉,草丛三婊之 ...

  4. jdk1.8 HashMap扩容机制变化

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

  5. 关于HashMap扩容机制

    HashMap的底层有数组 + 链表(红黑树)组成,数组的大小可以在构造方法时设置,默认大小为16,数组中每一个元素就是一个链表,jdk7之前链表中的元素采用头插法插入元素,jdk8之后采用尾插法插入 ...

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

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

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

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

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

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

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

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

最新文章

  1. LSTM情感分类问题再战
  2. 3、vue-router之什么是动态路由
  3. 【译】Ethereum Wallet in a Trusted Execution Environment / Secure Enclave
  4. local class cannot see outer class defined type, even if it is public
  5. day20 Python 高阶函数,函数,嵌套,闭包 装饰器
  6. expect脚本同步文件,expect脚本指定host和要同步的文件,构建文件分发系统,批量远程执行命令...
  7. C语言——机器平台对强制类型转换的影响
  8. 利用计算机教学的体会,教师计算机教学学习体会
  9. can 升级软件 上位机 C# 源码 支持STM32升级 提供源码 提供CAN协议
  10. 深入Elasticsearch:索引的创建
  11. 解决phpmyadmin加载慢问题
  12. 史上最严重网络数据泄露事件合集
  13. 我们应该怎样来提高自己的编程能力?
  14. 获取海康摄像机/录像机rtsp视频流地址格式
  15. 学习Maya学习MayaArnoldArnold
  16. Marvell 交换芯片DSA(分布式交换架构)功能介绍
  17. 第十三周 任务三
  18. 【CF940E】Cashback(单调队列dp)
  19. 【安全研究】Linux后渗透常见后门驻留方式分析
  20. 内存泄露检测详细分析

热门文章

  1. Nacos实现高可用
  2. 手机视频文件怎么压缩变小
  3. 科沃斯扫地机器人底部电源开关_科沃斯扫地机器人常见故障解决方法
  4. Liunx系统编程篇—进程通信(五)信号
  5. Servlet+JDBC+商品列表查询和修改
  6. 【数据挖掘实验】Clementine概述、记录操作、字段操作与图形的绘制
  7. iphone禁用提交按钮_如何在iPhone的锁定屏幕上禁用Siri建议
  8. matlab绘图配色
  9. 多线程经典面试题总结
  10. 一场关于数码宝贝的误解