HashMap
底层数据
- JDK1.7之前数组加链表
- JDK1.8之后数组加(链表或者红黑树)
为何要用红黑树,何时会树化,何时退化成链表?
当同时满足数组长度大于等于64,链表长度大于阈值8时,链表会树化
为什么也使用红黑树
1.树化是一种特殊情况,是为了避免链表超长的时候性能下降,防止DOS大量数据攻击
2.hash表的查找,更新时间复杂度为O(1),而红黑树的时间复杂度为O(log2N),TreeNode也比普通Node大,如果不是必要情况还是用链表
3.hash值如果足够随机的话,在负载因子为0.76的情况下,长度超过8的链表出现概率为0.0000006,选择8就是为了让树化概率更小
退化情况
1.在扩容拆分树的时候,树元素个数<=6则会退化成链表
2.remove时,若root、root.left、root.right、root.left.left有一个为null,退化
索引如何计算?hashCode都有了,为何还要提供hash()方法?数组容量为何是2的n次幂?
1.先计算对象的hashcode(),再进行调用hash()方法进行二次哈希,最后&(capacity-1)得到索引
2.二次hash()是为了综合高位数据,让哈希分布更均匀
数组容量为何是 2 的 n 次幂
- 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模
- 扩容时重新计算索引效率更高: hash & oldCap == 0 的元素留在原来位置 ,否则新位置 = 旧位置 + oldCap
注意
- 二次 hash 是为了配合 容量是 2 的 n 次幂 这一设计前提,如果 hash 表的容量不是 2 的 n 次幂,则不必二次 hash
- 容量是 2 的 n 次幂 这一设计计算索引效率更好,但 hash 的分散性就不好,需要二次 hash 来作为补偿,没有采用这一设计的典型例子是 Hashtable
Put与扩容因子
Put流程
1.HashMap是懒加载创建数组,只有第一次使用才创建数组
2.计算索引
3.如果索引位置没人占用,创建Node并且返回
4.如果索引位置有人占用 :
1.已经是TreeNode走红黑树逻辑
2.是普通Node走链表逻辑,如果超过链表树化阈值就树化
5.返回前检查是否容量超过阈值,如果超过就扩容
扩容(加载)因子为何默认是 0.75f
- 在空间占用与查询时间之间取得较好的权衡
- 大于这个值,空间节省了,但链表就会比较长影响性能
- 小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多
多线程情况下会有什么问题
1.扩容死链(1.7)
2.数据错乱(1.7、1.8)