数据结构之HashMap源码详解(四)resize扩容方法源码分析

第一篇文章传送门
第二篇文章传送门
第三篇文章传送门
为什么要扩容
当HashMap中的结点少时,进行查询是比较快的,但是当结点越来越多时,Hash表链化严重,复杂度由O(1)变为O(N),大大增加了查询的时间,通过扩容可以缓解该问题。
树化后的时间复杂度为O(log N)。
resize代码:

final HashMap.Node<K, V>[] resize() {
        
        HashMap.Node<K, V>[] oldTab = this.table;
        
        int oldCap = oldTab == null ? 0 : oldTab.length;
        
        int oldThr = this.threshold;
        
        int newThr = 0;
        
        int newCap;
       
        if (oldCap > 0) {
           
            if (oldCap >= 1073741824) {
                this.threshold = 2147483647;
                return oldTab;
            }

            if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
                newThr = oldThr << 1;
            }
        }
       
        else if (oldThr > 0) {
            newCap = oldThr;
        }
       
            newCap = 16;
            newThr = 12;
        }
     
        if (newThr == 0) {
            float ft = (float)newCap * this.loadFactor;
            newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
        }

        this.threshold = newThr;
       
        HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
        this.table = newTab;
        
        if (oldTab != null) {
            for(int j = 0; j < oldCap; ++j) {
             
                HashMap.Node e;
              
                if ((e = oldTab[j]) != null) {
                    
                    oldTab[j] = null;
               
                    if (e.next == null) {
                        
                        newTab[e.hash & newCap - 1] = e;
                    }
                 
                    else if (e instanceof HashMap.TreeNode) {
                        ((HashMap.TreeNode)e).split(this, newTab, j, oldCap);
                    }
                   
                    else {
                       
                        HashMap.Node<K, V> loHead = null;
                        HashMap.Node<K, V> loTail = null;
                       
                        HashMap.Node<K, V> hiHead = null;
                        HashMap.Node hiTail = null;
                        //
                        HashMap.Node 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;
                            }

                            e = next;
                        } while(next != null);
                     
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }

                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }

        return newTab;
    }

oldTab:引用扩容前的哈希表
oldCap:表示扩容前table数组的长度
oldThr:表示扩容之前的扩容阈值,
newThr:扩容之后的阈值
newCap:扩容之后table数组的大小

if (oldCap > 0) {
            
            if (oldCap >= 1073741824) {
                this.threshold = 2147483647;
                return oldTab;
            }

            if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
                newThr = oldThr << 1;
            }
        }

oldCap是扩容前数组的大小,如果扩容前数组不为空,那么就说明散列表之前已经初始化过了,是一次正常的扩容。
如果扩容达到了HashMap最大阈值,那么下次的扩容条件就改为int的最大值。
正常情况下是不会达到最大阈值的,所以,当不达到最大阈值的时候,那么就将阈值翻一倍。二进制中向左移动一位就代表数字翻倍。

else if (oldThr > 0) {
            newCap = oldThr;
        }

oldCap既然有大于0的情况,那么就有等于0的情况,
当调用这两个构造方法时,会出现中两种情况。

//1、new HashMap(initCap,loadFactor)
        //2、new HashMap(initCap)

当oldCap为0,说明HashMap为null,当oldThr大于0时,就将oldThr赋值给newCap。

else {//这是oldCap等于0的情况
            newCap = 16;
            newThr = 12;
        }

当oldThr为0的时候,这个时候调用的构造方法中只有加载因子的参数。此时就将HashMap初始化,赋值给它长度和扩容阈值。

if (newThr == 0) {
            float ft = (float)newCap * this.loadFactor;
            newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
        }
this.threshold = newThr;

当newThr为0时,通过newCap和loadFactor计算出一个newThr。将newThr赋值给threshold。
上面一系列就是获取扩容之后数组的长度等。接下来就是如何处理原HashMap中的节点。
我们知道,在HashMao中,可以存在单个节点,也可以是链表的形式,更可以是树。需要判断节点的不同形式。

 HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
        this.table = newTab;
      
        if (oldTab != null) {

如果oldTab不为空,那么就表示扩容之前的table不为空。

for(int j = 0; j < oldCap; ++j) {
               
                HashMap.Node e;if ((e = oldTab[j]) != null) {
                   
                    oldTab[j] = null;
                    
                    if (e.next == null) {
              
                        newTab[e.hash & newCap - 1] = e;
                    }

e表示当前node的节点。
如果(e = oldTab[j]) != null) ,说明当前位置有数据,但是数据具体是单个数据还是链表数据或者是树,还不知道。
oldTab[j] = null;置为空方便JVM回收。
e.next == null) ;如果向下走不为空,那么就说明当前节点是单个数据。那么就找到在新的HashMap中的位置。

判断当前位置有没有被树化。

else if (e instanceof HashMap.TreeNode) {
                        ((HashMap.TreeNode)e).split(this, newTab, j, oldCap);
                    }

没有被树化就代表是链表,毕竟单个节点和树化在前面已经判断过了。
设置低位链表和高位链表的头尾节点。
低位链表:存放在扩容之后的数组的下标,与当前数组的下标位置一致。
高链位表;存放在扩容之后的数组的下标位置为 当前数组下标位置+扩容之前数组的长度。

/
                    else {
                        
                        HashMap.Node<K, V> loHead = null;
                        HashMap.Node<K, V> loTail = null;
                      HashMap.Node<K, V> hiHead = null;
                        HashMap.Node hiTail = null;
                        //
                        HashMap.Node next;

假如长度为默认的16,二进制为4位表示,扩容后用5位二进制进行表示,如果首位为0,表示为低链位表,

if ((e.hash & oldCap) == 0) {
                                if (loTail == null) {
                                    loHead = e;
                                } else {
                                    loTail.next = e;
                                }

                                loTail = e;
                            } 

表示低链位表有数据,如果有数据,那么就将下一位置空,重新选择位置插入。

if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值