HashMap死循环及JDK1.8的resize()如何维护链表顺序

HashMap死循环

我记得第一次听到HashMap会有死循环的问题是两年前,我们团队有一个学长实习回来给我们说有个同事因为没有注意到HashMap在多线程可能会造成死循环而造成了一些损失。后面就对这个问题很感兴趣,然后趁着今天整理资料就顺便记录一下,虽然偷懒了,逃 = -=

什么造成的

我们知道HashMap不是线程安全的,那么具体到代码,是哪部分代码的原因呢?
我本来想写一篇博客介绍如何死循环产生的原因及过程,然后再查资料的时候发现了美团点评的这篇博客:Java 8系列之重新认识HashMap中介绍死循环的部分已经写的非常清楚了,就不想做重复工作了 = -=,哈哈
但是呢,最主要的问题解决了,我就只好分析一下JDK1.8中是如何做到扩容后链表依然保持着原来的顺序喽~哈哈

resize()方法

JDK1.8的改变

相较于JDK1.7,在1.8中resize()方法不再调用transfer()方法,而是直接将原来transfer()方法中的代码写在自己方法体内;
当然表面上我们能看到方法的减少,其实还有一个重大改变,那就是:扩容后,新数组中的链表顺序依然与旧数组中的链表顺序保持一致!

数组长度的技巧

在分析源码之前,我们还需要知道HashMap底层数组的一些优化:
数组长度总是2的倍数,扩容则是直接在原有数组长度基础上乘以2。

为什么要这么做?有两个优点:
1. 通过与元素的hash值进行与操作,能够快速定位到数组下标
相对于取模运算,直接进行与操作能提高计算效率。在CPU中,所有的加减乘除都是通过加法实现的,而与操作时CPU直接支持的。
2. 扩容时简化计算数组下标的计算量
因为数组每次扩容都是原来的两倍,所以每一个元素在新数组中的位置要么是原来的index,要么index = index + oldCap,假设


数组长度:2,key:3
0 0 0 1
0 0 1 1 结果为 0 0 0 1 = 1 所以数组下标为1;

扩容后,数组长度:4,key:3
0 0 1 1
0 0 1 1 结果为 0 0 1 1 = 3 = 原来的index + oldCap = 1 + 2


即确定元素在新数组的下标时,我们只需要检测元素的hash值与oldCap作与操作的结果是否为0:为0,那么下标还是原来的下标;为1,那么下标等于原来下标加上旧数组长度。

我们来看关键代码(resize()方法完整代码附后):

//如果扩容后,元素的index依然与原来一样,那么使用这个head和tail指针
Node<K,V> loHead = null, loTail = null
//如果扩容后,元素的index=index+oldCap,那么使用这个head
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值