HashMap灵魂拷问你必须顶住
# 前言
如果你是刚工作或者工作两三年,那HashMap基本上是必问八股文之一。下面是常见的问题,基本已经涵盖面试中最常见的问题。答案比较精简,可能不详细,可针对问题进行自行延伸。
# 1.HashMap的数据结构
jdk1.7采用数组+链表,数组中存key-value结构叫Entry
jdk1.8采用数组+链表+红黑树,数组中存key-value叫Node
# 2.为什么需要用到链表
解决hash冲突,数组长度是有限的,在有限的长度里面我们使用哈希,哈希本身就存在概率性。
# 3.链表的插入方式
1.7采用头插法,并发情况下会有死循环的问题,1.8采用尾插法解决。
死循环的原因:扩容转移后,前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。
# 4.HashMap扩容机制
数组容量是有限的,数据多次插入,到达一定数量就会进行扩容。
- Capacity:HashMap当前长度;
- LoadFactor:负载因子,默认值是 0.75f 。
HashMap的扩容分为两步:
- 扩容:创建一个新的 Entry 空数组,长度是原数组的2倍;
- ReHash:遍历原 Entry 数组,把所有的 Entry 重新 Hash 到新数组。
# 5.为什么要rehash?
hash公式:index = HashCode(key)&(Length - 1) 长度扩大后规则也随之改变。
# 6.HashMap的默认初始化长度
初始化大小是16
# 7.为什么是16?
因为在使用不是2的幂的数字时,Length - 1 的值是所有二进制位全为1,这种情况下,index 的结果等同于 HashCode 后几位的值。(15的二进制是 1111) 只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这是为了实现均匀分布。
# 8.ConcurrentHashMap
ConcurrentHashMap采用了分段锁技术,其中Segment继承于ReetrantLock。不会像HashTable那样不管是put还是get操作都需要做同步处理,理论上ConcurrentHashMap支持Segment数组数量的线程并发。 每当一个线程占用锁访问一个Segment时,不会影响到其他的Segment。
# 9.HashMap与ConcurrentHash
- ConcurrentHash是线程安全的,HashMap是线程不安全的
- ConcurrentHash不允许null作为key/value,HashMap允许一个key为null,value不限制
# 10.ConcurrentHash1.7和1.8区别
1.7: Segment分段锁,Segment继承于ReetrantLock 核心数据如value,以及链表都是volatile修饰的,保证了获取时的可见性。
1.8: 抛弃了1.7的分段锁的设计,而采用了CAS + synchronized来保证并发安全性。 value、next都采用了volatile修饰,保证了可见性。 取消了segment数组,直接用table保存数据,锁的粒度更小,减少并发冲突的概率
# 11. ReentrantLock和Synchronized的区别
相同点:
- 它们都是加锁方式同步;
- 都是重入锁;
- 阻塞式的同步