Map 重要面试题

HashMap 的底层实现

JDK 1.8 之前

JDK 1.8 之前 HashMap 底层是 数组和链表 结合在一起使用的。HashMap 通过 key 的 hashcod 经过扰动函数处理过后的 hash 值,然后通过 ( n - 1 ) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前元素存在元素的花,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决。

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

JDK 1.8 之后

相比于之前的版本,JDK 1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)(将链表转化成红黑树之前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转化为红黑树)时,将链表转化为红黑树,以减少搜索时间。

HashMap 的长度为什么是 2 的幂次方

为了能让 HashMap 存取高位,尽量减少碰撞,也就是要尽量把数据分配均匀。因为 Hash 值范围是 2^-31 ~ 2^31 -1 ,前后加起来大概 40 亿的映射空间,只要哈希函数映射的比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先对数组的长度取模运算,得到的余数才能用来表示要存放的位置,也就是对应数组的下标。并且每次增加 2 的幂次方倍可以有效防止哈希碰撞的问题

HashMap 多线程操作导致的死循环问题

JDK 1.7 之前版本的 HashMap 在多线程环境下扩容操作可能会存在死循环问题,这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的地方,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。

为了解决这个问题,JDK 1.8 版本的 HashMap 采用了尾插法而不是头插法来避免链表导致,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。

JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现由什么不同

  • 线程安全实现方式: JDL 1.7 采用 segment 分段所来保证安全。JDK 1.8 放弃了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更精细。

  • Hash 碰撞解决办法: JDK 1.7 采用拉链法,JDK 1.8 采用拉链法结合红黑树

  • 并发度: JDK 1.7 最大并发度是 Segment 的个数,默认是 16 。JDK 1.8 最大并发度是 Node 数组的大小,并大度更大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值