1. 程式人生 > >HashMap原始碼-擴容resize方法

HashMap原始碼-擴容resize方法

環境:jdk1.8
resize方法原始碼如下
final Node<K,V>[] resize() 
{
    Node<K,V>[] oldTab = table;
    //獲取舊錶的容量值
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //獲取舊的閾值
    int oldThr = threshold;
    //定義新的容量、新的閾值
    int newCap, newThr = 0;
    //1.判斷舊錶容量值是否大於0
    if (oldCap > 0) 
    {
    	//判斷舊錶容量值是否大於最大容量值,如果大於或等於,則閾值取:2<<30,且返回舊錶,這裡相當於只是調整了閾值大小,沒有對舊錶大小進行實際改變
        if (oldCap >= MAXIMUM_CAPACITY) 
        {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //初始化新容量值為舊容量值的2倍,然後與最大容量值比較,當新容量值小於最大容量值,且舊容量值大於等於預設初始化容量值,這時也對新閾值賦值為舊閾值的2倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    //2.這裡的前提條件是oldCap=0,說明這是第一次建立表,將對新容量賦值為舊閾值,這裡舊閾值其實就等於HashMap的初始化容量值
    else if (oldThr > 0)
        newCap = oldThr;
    //3.當oldCap=0且oldThr=0的情況下,我們預設對新容量值和新閾值按初始化值進行賦值
    else 
    {
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //當進入判斷條件2時,這時新閾值就會等於0
    if (newThr == 0) 
    {
    	//根據新容量值乘以載入因子得到新閾值
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //修改全域性變數threshold的值
    threshold = newThr;
	//定義並初始化新表,指定容量
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //修改全域性變數table的值
    table = newTab;
    //判斷舊錶是否存在,如果存在,則需要將舊錶中的資料複製到新表中
    if (oldTab != null) 
    {
    	//遍歷舊錶
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //判斷舊錶中的每個位置上的元素是否存在,將該元素賦值給臨時變數e
            if ((e = oldTab[j]) != null) 
            {
            	//將舊錶元素置空,有利於GC
                oldTab[j] = null;
                //因為HashMap的資料結構是陣列+連結串列,這裡需要判斷該元素所在連結串列是否有下個元素,如果沒有,則可以直接將該元素放入新表中
                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;
                        //比較hash值,這裡的目的是為了處理hash衝突,使用hiHead儲存雜湊衝突的元素
                        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;
}
總結1、擴容操作首先主要的操作是給新表的容量以及閾值正確賦值2、建立新表後,要判斷節點元素是紅黑樹還是連結串列,分別做不同的處理3、處理連結串列元素時要注意雜湊衝突的元素要單獨提取出來儲存