HashMap(3)进阶篇--HashMap扩容机制

1.什么是resize:

resize就是重新计算容量;当我们不断的向HashMap对象里不停的添加元素时,HashMap对象内部的数组就会出现无法装载更多的元素,这是对象就需要扩大数组的长度,以便能装入更多的元素;当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组;就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

2.什么时候需要resize():

当向容器添加元素的时候,会判断当前容器的元素个数,如果大于等于阈值—即当前数组的长度乘以加载因子的值的时候,就要自动扩容。

扩容:(源码 661-662)

《HashMap(3)进阶篇--HashMap扩容机制》

计算阀值:

1.第一次创建Hash表时:

《HashMap(3)进阶篇--HashMap扩容机制》

2.对HashMap进行扩容时:

《HashMap(3)进阶篇--HashMap扩容机制》

3.源码分析:

final Node<K,V>[] resize() {
    //保存旧的 Hash 数组
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        //超过最大容量,不再进行扩充
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //容量没有超过最大值,容量变为原来的两倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
                 //阀值变为原来的两倍
            newThr = oldThr << 1; 
    }
    else if (oldThr > 0) 
        newCap = oldThr;
    else {
    //阀值和容量使用默认值
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
    //计算新的阀值
        float ft = (float)newCap * loadFactor;
        //阀值没有超过最大阀值,设置新的阀值
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    //创建新的 Hash 表
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    //遍历旧的 Hash 表
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                //释放空间
                oldTab[j] = null;
                //当前节点不是以链表的形式存在
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                    //红黑树的形式,略过
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { 
                //以链表形式存在的节点;
                //这一段还是看下面的图解吧,搞了好久才懂得 ^_^
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                    //最后一个节点的下一个节点做空
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                    //最后一个节点的下一个节点做空
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

以链表形式存在的节点:

存在两个数他们的 Hash 值分别为:5,21 二进制形式分别为(0101,10101)。

若没有进行扩容时容量为 16,进行扩容之后的容量为 32

坐标点的计算(计算规则 :e.hash & (newCap – 1)):

没有进行扩容时:

《HashMap(3)进阶篇--HashMap扩容机制》

可以看到两个Hash值所计算的坐标是相同的。

进行扩容之后:

《HashMap(3)进阶篇--HashMap扩容机制》

可以看出经过扩容之后,两次计算的坐标出现了不同,但是第二个坐标点增加了 oldCap 个长度。

再看看 e.hash & oldCap 所计算出的结果:

《HashMap(3)进阶篇--HashMap扩容机制》

可以看到当 e.hash & oldCap == 0 是,原来的坐标没有发生变化,e.hash & oldCap != 0 在原来坐标的前提下增加 oldCap 。

两条链表的连接过程:

《HashMap(3)进阶篇--HashMap扩容机制》

《HashMap(3)进阶篇--HashMap扩容机制》

《HashMap(3)进阶篇--HashMap扩容机制》

《HashMap(3)进阶篇--HashMap扩容机制》

在向表中连接的时候最后一个节点的下一个节点做空。

总结:

  1. 在对 HashMap 进行扩容时,阀值会变为原来的两倍;
  2. 在对HashMap进行扩容的时候,HashMap的容量会变为原来的两倍;
  3. 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。
  4. 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。
    原文作者:ba拉扒la
    原文地址: https://blog.csdn.net/mgl934973491/article/details/60466487
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞