HashMap只有容量达到阀值才发生扩容吗?

原文链接地址:https://blog.csdn.net/u011328417/article/details/80728571


看了网上很多文章,说HashMap在元素达到负载因子对应数的时候就发生扩容。如果你看过源码就会发现,其实还有一种情况也可能会发生扩容:树形化的时候。

对象最终是如何放入HashMap中的? 
HashMap底层是由数组+链表组成的,为了方便不懂的人更容易理解,那我们就先假设HashMap底层就是数组,先不管链表。 
当一个对象add到HashMap中,此时HashMap的add方法是如何来确定这个对象是放在数组中的哪个位置的呢?

拿JDK1.8来说(其他JDK版本稍有不同,但大同小异),大家应该知道每一个对象天生都继承了或程序员自己覆盖了Object类的 hashCode()方法,此方法返回对象的hashcode值。 
HashMap会有一个方法,先拿到要add进HashMap中的对象的hashCode,再将这个hashCode异或上对象自身hashCode右移16位(是不是感觉说的不是人话?这个步骤叫扰乱,这样做的目的是为了让hashCode每一位都尽可能用到,如果不理解没关系并不影响接下来的阅读),hashCode经过上述步骤之后再&(数组长度-1),计算的结果就是这个对象在数组中的位置了。我自己都觉得说的不是人话,下面举个例子,便于理解:

这里有一个Student对象的hashCode是:a 
先把这个a右移16位 , b=a>>>16; 
然后a=a&b; 
数组中的位置等于: a&(数组长度-1); 
上述源码如下:

h=key.hashCode();
h = key.hashCode()) ^ (h >>> 16)
数组位置=h&(数组长度-1);

好了, 我们已经知道元素是如何在hashMap中的数组上如何定位了,现在假设一个极端情况(不可能发生,但是我用这个举例子): 
假设数组长度为1,根据源码: 
数组位置=h&(数组长度-1) 
那么有: 
数组位置=h&(1-1)=0 ,无论什么对象,都定位到数组的第0个位置。 
这个很好理解吧。无论元素是否一样,由于数组长度为1,所以元素通通定位到数组中第0个位置。大家都知道一个数组只能放一个元素啊?那怎么办呢?我们用链表来解决这个问题,把定位到这个位置的元素通过链表连接。这就是我一开始说的:hashMap是数组+链表。

那树形化又是什么东东呢? 
想一下我们为什么要用HashMap,是因为通过Hash算法在理想情况下时间复杂度O(1)就能找到元素,特别快,但是我都说了是理想情况,如果遇到上述发生hash碰撞(谁jb取的名字,就是上面我才说的,两个元素定位到数组中同一个位置),且hash碰撞比较频繁的话,那么当我们get一个元素的时候,定位到了这个数组,还需要在数组中遍历一次链表最终才能找到要get的元素,是不是已经失去一部分使用HashMap的初心了?(因为需要遍历链表,所以时间复杂度就比之前高了) 
所以JDK1.8使用红黑树这种数据结构来解决链表过长的问题(可以简单理解为用红黑树遍历比链表遍历速度快,时间复杂度低,不懂红黑树的可以去搜搜看),默认链表长度达到8就将链表树形化(变为红黑树)。

回到最最开始我提到的,那为什么树形化的时候可能会发生扩容呢? 
想想刚刚的例子数组长度为1,所有元素全部在数组的第0个位置形成一条链表,这例子是一种极端情况,数组长度过小,那自然就会经常发生hash碰撞,那形成长链表是肯定的,这个时候树形化其实是治标不治本,因为引起链表过长的根本原因是数组过短,所以在JDK1.8源码中,执行树形化之前,会先检查数组长度,如果长度小于64,则对数组进行扩容,而不是进行树形化。

所以发生扩容的时候有两种情况,一种是元素达到阀值了,一种是HashMap准备树形化但又发现数组太短,这两种情况均可能发生扩容。

HashMap容量扩容

有几个重要的常量: static final int DEFAULT_INITIAL_CAPACITY = 1 4;//默认的桶数组大小 static final int MAXIMUM_CAP...

猜你喜欢

转载自blog.csdn.net/yu757371316/article/details/82660788
今日推荐