【聊聊Java】Java中HashMap常见问题 -- 扩容、树化、死链问题

写在前边

  • HashMap属于比较常用的数据结构了,面试过程中也经常会被问到,本篇就知识点,展开问答式分析,重点聊聊hash冲突、扩容死链、容量为2的n次方等问题~

1.7和1.8有什么不同

1.7是 数组+链表 1.8是 数组+链表【超过阈值会变成红黑树】

如何解决Hash冲突问题

扩容

条件

  1. 链表长度超过8
  2. 元素个数超过数组个数的75%

树化规则

条件

  1. 链表长度超过8
  2. 此时看看数组长度是否超过64,超过就进行树化,否则只是单纯扩容

为什么需要树化

其实一般正常的元素,都是不会超过阈值的,只有插入一堆重复的元素,hash值一样,才可能达到阈值,这个简称Dos攻击 而元素一旦多起来,链表查找的效率就远不及红黑树了

🤷‍♂️树化一定更好吗?

不是的,维护红黑树需要占用比链表更多的空间,而且当链表长度足够短的时候,链表查找的效率反而比红黑树更高??

为什么选择0.75和8

  • hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,树化阈值选择 8 就是为了让树化几率足够小

退化规则

扩容的时候链表长度<=6

remove节点的时候

root、root.left、root.right、root.left.left 有一个为 null ,也会退化为链表(看的是移除之前的情况

为什么需要二次哈希

先获得key的hashCode的值 h,然后 hh右移16位 做异或运算。
实质上是把一个数的末x位低16位与他的高16位做异或运算,因为在前面 (n - 1) & hash 的计算中,hash变量只有末x位会参与到运算。使高16位也参与到hash的运算能减少冲突

🍔为什么要用2的n次方

为了方便 & 操作

只有2的n次方,去-1,才能用 & 替代 %

🎈为了方便扩容

扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 否则新位置 = 旧位置 + oldCap (oldCap:原始的容量)

因为HashMap的初始容量是2的次幂,扩容之后的长度是原来的二倍,新的容量也是2的次幂,所以,元素,要么在原位置,要么在原位置再移动2的次幂

看下这张图,n为table的长度 图a表示扩容前的key1和key2两种key确定索引的位置 图b表示扩容后key1和key2两种key确定索引位置。

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

所以在扩容时,只需要看原来的hash值新增的那一位是0还是1就行了【直接 hash & oldCap,就能知道是0还是1了】 是0的话索引没变,是1的话就变成原索引+oldCap

不用2的n次方可以吗

可以的,因为2的n次方也会有缺陷,比如给定的值全是偶数,无论如何hash之后取模,都是偶数,分布就不均匀

此时如果用质数作为容量的话,就会分布得比较均匀

注意

二次 hash 是为了配合 容量是 2 的 n 次幂 这一设计前提,如果 hash 表的容量不是 2 的 n 次幂,则不必二次 hash

容量是 2 的 n 次幂 这一设计计算索引效率更好,但 hash 的分散性就不好,需要二次 hash 来作为补偿,没有采用这一设计的典型例子是 Hashtable

并发扩容丢失数据问题

主要是第一个节点才会吧?因为第一个是new Node出来的

jdk1.7 并发扩容死链问题

jdk1.7中,采用的是头插法,用一个e指针表示当前要扩容的节点,next表示接下来要扩容的节点,一直头插e更新e为next,直到e为null

假设现在有两个线程1和2,要扩容一个Map

  1. 线程1的局部变量e,指向了a节点,next指向b节点
  2. 线程2的局部变量也是如此,此时线程2先进行扩容,由于是头插法,最终结果变成了 b->a
  3. 但此时来到线程1先进行,局部变量不会受改变,e还是指向a,next还是b,所以把a头插,并且更新e为next,也就变成了b
  4. 线程1继续头插b,没问题,结果变成了[b->a],看起来是没问题了,但是接下来判断e还没有next:
  5. 发现e的next是a,又要继续头插a,插完a之后,发现a的next又是b,寄了这下,无限循环了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值