一.实现
JDK7采用数组+链表。 JDK8采用数组+链表、红黑树。
二.知识点
- 初始容量为2^4=16。
- 负载因子为0.75
- JDK1.8开始,一个桶存储的链表长度大于8时会将链表转化成红黑树。
- 确定桶的下标:hashcode & (length - 1),length为当前容量,按位与操作。
- JDK7的链表插入以头插法进行的(扩容后顺序会相反,JDK8保持顺序)。
三.扩容
-
何时扩容? 默认键值对的数量大于等于(容量(桶)*负载因子)的个数时即扩容为原来的两倍。
-
实现过程 扩容使用resize()实现,扩容操作把oldTable的所有键值对重新插入newTable中。 扩容的时候需要重新计算桶的下标,容量(桶)为2的n次方极大降低桶下标操作的复杂度。对于一个key,16扩容为32只需看第五位,为0不变,为1+16。
五.问题
-
用户传入的初始容量不为2的n次方。 HashMap可以自动地将传入的容量转换为2的n次方。
-
为什么数组的长度一定为2的n次方? 只有长度为2的n次方的时候,我们对它减1才能拿到全部为1的二进制,这样才能在按位与操作中非常快速的拿到下标,并且分布也是均匀的。
-
Java7d HashMap有哪些问题? 1) 非常容易死锁,多线程扩容导致链表的值互相指向(头插法导致的),然后在查询的时候无此元素循环导致死锁(环形链表导致CPU100%)。 2) 潜在的安全隐患:可以通过精心构造的恶意请求引发Dos。链表的性能退化(产生大量链表,消耗大量cpu)。 3) 为什么大于8才能转换成红黑树? 根据泊松分布计算,参数为0.5,超过8的时候概率非常的小。
六.ConcurrentHashMap
线程安全的HashMap。
- HashTable通过使用Synchronized修饰方法的方式来实现多线程同步,因此,HashTable的同步会锁住整个数组,性能在高并发情况下非常差。
- ConcurrentHashMap采用锁分离技术,对应的put、remove等操作也只需要锁住当前线程需要用到的桶,而不需要锁住整个数据。
- JDK1.7采用Segment分段锁,1.8采用CAS+Synchronized来保证并发安全性。
- ConcurrentHashMap的Hash链中除了变量value外,其他变量都被定义为final类型,保证不加锁读取到一致的数据。(HashEntry链表)
七.参考视频
https://www.bilibili.com/video/BV18E411C7kD https://www.bilibili.com/video/BV1T54y1B7rV?p=37
评论