HashMap 源码resize () 扩容方法

Posted HaSaKing_721

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap 源码resize () 扩容方法相关的知识,希望对你有一定的参考价值。

前言

扩容的逻辑大概分为两步:

  • 1)计算新的容量和扩容阀值,并创建新的 table 数组
  • 2)将老的 table 复制到新的 table 数组中

源码

final Node<K,V>[] resize() 
	//将之前的数组进行保存
    Node<K,V>[] oldTab = table;
    //旧数组的长度
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //旧数组的扩容阈值(数组容量 * 加载因子(0.75))
    int oldThr = threshold;
    //初始化新数组的长度和阈值
    int newCap, newThr = 0;
    
    /** 第一步:计算新的容量和扩容阀值,并创建新的 table 数组 **/
    
    // 旧数组的长度 大于 0 ,说明 table 非空,进行两倍扩容
    if (oldCap > 0) 
        //超过最大容量,则直接设置 threshold 阀值为 Integer.MAX_VALUE ,不再允许扩容
        if (oldCap >= MAXIMUM_CAPACITY) 
            threshold = Integer.MAX_VALUE;
            return oldTab;
        
        // 新数组的长度 = 旧数组的长度<< 1 ,目的是两倍扩容
        // 如果 旧数组的长度 >= DEFAULT_INITIAL_CAPACITY 满足,说明当前容量大于默认值(16),则 2 倍阀值。
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // 可以理解为 newCap * loadFactor 位运算更快
    
    // 旧数组的扩容阈值 大于 0 ,则使用 旧数组的扩容阈值 作为新的容量
    else if (oldThr > 0) 
        newCap = oldThr;
    else // 旧数组的扩容阈值 等于或下雨 0 ,则使用 DEFAULT_INITIAL_CAPACITY 作为新的容量,使用 DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY 作为新的容量
        newCap = DEFAULT_INITIAL_CAPACITY;//长度为16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//阈值为16*0.75
    
    //如果上述的逻辑,未计算新的阀值,则使用 newCap * loadFactor 作为新的阀值
    if (newThr == 0) 
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    
    

	/** 第二步:将老的 table 复制到新的 table 数组中 **/
	
    // 将 新数组阈值 赋值给 threshold 属性
    threshold = newThr;
    // 创建新的 Node 数组,赋值给 table 属性
    @SuppressWarnings("rawtypes","unchecked")
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 如果老的 table 数组非空,则需要进行一波搬运
    if (oldTab != null) 
        for (int j = 0; j < oldCap; ++j) 
            // 获得老的 table 数组第 j 位置的 Node 节点 e
            Node<K,V> e;
            if ((e = oldTab[j]) != null) 
                // 置空老的 table 数组第 j 位置
                oldTab[j] = null;
                // 如果 e 节点只有一个元素,直接赋值给新的 table 即可
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                //如果 e 节点是红黑树节点,则通过红黑树分裂处理
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                //如果 e 节点是链表
                else  // preserve order
                    // HashMap 是成倍扩容,这样原来位置的链表的节点们,会被分散到新的 table 的两个位置中去
                    // 通过 e.hash & oldCap 计算,根据结果分到高位、和低位的位置中。
                    // 1. 如果结果为 0 时,则放置到低位
                    // 2. 如果结果非 1 时,则放置到高位
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    // 这里 do while 的原因是,e 已经非空,所以减少一次判断。细节~
                    do 
                        // next 指向下一个节点
                        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);
                    // 设置低位到新的 newTab 的 j 位置上
                    if (loTail != null) 
                        loTail.next = null;
                        newTab[j] = loHead;
                    
                    // 设置高位到新的 newTab 的 j + oldCap 位置上
                    if (hiTail != null) 
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    
                
            
        
    
    return newTab;

流程图

//TODO

以上是关于HashMap 源码resize () 扩容方法的主要内容,如果未能解决你的问题,请参考以下文章

HashMap底层源码解析下(超详细图解)

看完彻底了解 HashMap 源码

jdk1.8中hashmap的扩容步骤resize

jdk1.8 HashMap源码分析(resize函数)

JDK1.7中HashMap死环问题及JDK1.8中对HashMap的优化源码详解

jdk1.8 HashMap的扩容resize()方法详解