hashmap底层原理-核心要点

 

1.基本原理?

jdk1.8 : 数组 + 链表/红黑树

hashmap的初始默认容量:16,如果new Hashmap的时候指定了,也必须是2的幂次方

hash冲突时:先使用链表解决冲突--->链表的长度到8,则使用红黑树

hash算法:先高16位异或低16位再取模运算 why?参考:https://blog.csdn.net/weixin_34288121/article/details/93446872

先高16位异或低16位再取模运算的目的:减少hash冲突

默认扩容阈值/负载因子:0.75f

 

2.如何put?

1).寻找slot

2).根据情况插入

2.1 slot为null,没有hash冲突,直接插入

2.2 slot为单节点:hash冲突,变成链表

2.3 slot为链表,hash冲突,在链表的尾部追加节点

2.4 slot为红黑树,将节点插入红黑树(涉及到红黑树的概念、原则、重新构建,左旋右旋等过程....)

3).hashmap容量不够,达到条件需要先扩容

 

3.hashmap什么时候扩容?

扩容是懒加载类型的:put的时候确定是否扩容,扩容时候直接生成一个容量为2倍的数组,然后进行数据迁移

影响发生Resize的因素有两个:

1)LoadFactor:HashMap负载因子,默认是0.75f。

当 HashMap.size >= Capacity*LoadFactor 时,HashMap可能会进行Resize。

2)当前数据存储的数量(即size())大小必须大于等于阈值(例如初始值16,负载因子0.75,阈值为12);

当前加入的数据是否发生了hash冲突。

 

因为上面这两个条件,所以存在下面这些情况

(1)就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。

(2)当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。

 

4.如何扩容?

1)创建一个新的Entry空数组,长度是原来的2倍

2)数据迁移:遍历原Entry数组,把所有的Entry重新Hash到新数组里。

为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变了。

让我们了解一下Hash公式:

jdk1.8 : index = HashCode(key) & (Length - 1)

jdk1.7:  index = HashCode(key)% Length

当你经过运算的时候,你就会发现,两种方式得到的结果是一致的,所以都正确。

注意:jdk1.7版本下,HashMap并不是线性安全的,在并发的情况下可能会形成链表环。

static final int hash(Object key) {   //jdk1.8 & jdk1.7
     int h;
     // h = key.hashCode() 为第一步 取hashCode值
     // h ^ (h >>> 16)  为第二步 高位参与运算
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

 

5.扩容时如何数据迁移?

1.slot为null,表示没有数据,不需要操作

2.slot为单个节点,表示正常数据,直接拿到新数组相同下标的位置即可

3.slot为链表:拆分高低位链表,低位链表放到新数据的相同下标位置,高位链表放到新数组的 原slot位下标+原数组长度  位置

4.slot为红黑树:与链表相似,因为hashmap中的红黑树不只是红黑树,同时还维护了一个链表,用来数据迁移使用;同样拆分出高低位链表--------不同的是,如果高低位链表,长度大于6,则需要重新转化为红黑树

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章