HashMap JDK1.8源碼分析 v0.1

第一節 參考
1. https://segmentfault.com/a/1190000012728513

第二節 架構
一.關注點
1.jdk1.7以前使用數組,鏈表實現.1.8之後使用數組,鏈表,紅黑樹.
2.threshold=capacity * load factor. 當size個數到達threshold而且目標hash值初不爲空時,做resize擴容,擴容2倍.
3.鏈表頭插法,擴容後鏈表倒置
4.取模通過h & (length - 1).
5.length取大於長度的2的冪次方的數值,算法是位操作,取出最高位的1的位置,較低的位全是1.
6.索引值。取低位的哈希值,調用 key & (length - 1)
7.重新hash基本不會觸發,需要元素個數大於Inter.Max_value.
8.多線程擴容死循環問題.
9.因爲沒有重新算hash值,同一hash值的鏈表還是在擴容之後的數組的同一位置的鏈表處,只是鏈表倒置.
10.modcount引起的concurrentModifyException異常.
11.jdk1.8上,如果節點個數大於TREEIFY_THRESHOLD,單鏈錶轉爲紅黑樹存儲或者拆鏈表擴容,TREEIFY_THRESHOLD默認爲8.


二.紅黑樹
(一).性質
二叉查找樹的一種,避免普通二叉查找樹有序時退化成單鏈表
1.節點是紅色或者黑色
2.根節點是黑色
3.每個葉子節點(NIL)是黑色
4.紅色節點的兩個子節點是黑色
5.任一節點到每個葉子都有相同數目的黑色節點.

(二).插入節點
1.1.8之後的紅黑樹左旋,右旋三步走.
左旋(左邊少樹):頂點往左下來,右邊子節點上去做根,右邊子節點的左子樹左旋掛到原來頂點的右子樹上。
右旋(右邊少樹):頂點往右下來,左邊子節點上去做根,左邊子節點的右子樹右旋掛到原來頂點的左子樹上.
2.新插入節點默認爲紅色。如果插入黑色節點,當前路徑比其他路徑多出黑色節點,調整麻煩.
插入場景分別如下:
a.如果只有一個節點,即根節點黑色.如果只有兩個或者三個節點,根節點黑色,子節點紅色.
b.紅黑樹變色.條件:新插入節點的父節點和叔叔節點是紅色.
變色:父節點和叔叔節點變黑色,爺爺節點變紅色.
c.紅黑樹左旋.條件:不滿足規則的連續紅色節點的下面節點是右邊子節點,它的父親是紅色,叔叔是黑色.
左旋:不滿足規則的連續紅色節點的上面節點作爲頂點,左旋.左旋不變色.
d.紅黑樹右旋.條件:不滿足規則的連續紅色節點的下面節點是左邊子節點,它的父親是紅色,叔叔是黑色.
右旋:不滿足規則的連續紅色節點的上面節點的父節點作爲頂點,即下面節點的爺爺節點作爲頂點,右旋.右旋需要多一個變色操作,
父節點右旋上去後變黑,爺爺節點右旋下來後變紅.
(三).刪除節點

第三節 源碼細節
一.構造方法
如果HashMap()使用默認的空構造方法,只設置loadFactor爲0.75.閾值和容量都爲0.

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}


初始化初始容量(默認16),裝載因子(默認0.75),閾值(大於閾值時擴容2倍,默認16*0.75=12).
tableSizeFor方法用於返回大於容量的2次冪的值
默認的空構造方法在第一次put的resize時設置這些值.
 
二.put() 插入數據
hash是key的hashCode()方法值異或hashcode的高16位.
   

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        //第一次時table爲空,創建table的node數組,
        n = (tab = resize()).length;
    //索引i即是(n-1)&hash
    if ((p = tab[i = (n - 1) & hash]) == null)
        //看數組的該索引處是否爲空,如果爲空,創建單鏈表,
        //這裏即爲單鏈表的第一個節點
        tab[i] = newNode(hash, key, value, null);
    else {
        //該索引處已經有值,產生碰撞時,進入這裏.此時,p指向鏈表第一個節點
        Node<K,V> e; K k;
        //如果第一個節點的key,hash都和put值相等,代表直接value在後面放在第一個節點裏面
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //如果第一個節點是二叉樹節點,通過二叉樹插入值
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                //p向後遍歷,直到最後一個節點
                if ((e = p.next) == null) {
                    //把插入的節點放在最後一個節點後面
                    p.next = newNode(hash, key, value, null);
                    //binCount從0個數,這裏單鏈表節點個數從第9個開始,轉爲紅黑樹存儲或者擴容拆鏈表,
                    //即鏈表最多長度爲9,因爲上面一行已經把第9個節點放到鏈表尾部
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //如果遍歷過程中有節點和插入的值hash,key都相等,跳出循環
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //如果已經存在key,hash相同,則指向已有節點,此時e不爲空.不存在已有節點,則e爲空.
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //正在更新的節點個數加一,用於併發讀時,報錯
    ++modCount;
    //節點個數>閾值,擴容
    if (++size > threshold)
        resize();
    //下面這個空方法
    afterNodeInsertion(evict);
    return null;
}


三.resize() 擴容
如果HashMap()使用默認的空構造方法時,第一次put進入這裏創建node數組.

final Node<K,V>[] resize() {
    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;
        }
        //正常的擴容操作,原來的threshold默認是12,變成24,cap是16,變成32.
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {      
         // zero initial threshold signifies using defaults
        //使用默認的空構造方法時,容量設置爲16.閾值爲12.
        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;
    //創建節點數組,默認大小爲16.第一個次擴容在前面變成32.
    @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        //初始化設置不進這裏.第一次擴容時進入這裏,依次移動原來數組中的每個單鏈表
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //原數組中該索引處有元素,開始移動
            if ((e = oldTab[j]) != null) {
                //原來數組該索引處清空,節點賦給e
                oldTab[j] = null;
                if (e.next == null)
                //原數組該索引處單鏈表只有一個節點,根據hash值和新的cap容量重新計算節點索引位置
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    //原數組該索引處是紅黑樹,進行分裂操作
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    //第一次擴容,拆分鏈表進這裏
                    //單鏈表按照元素排序的奇偶分成兩個鏈表,
                    //loHead指向奇數位置鏈表.hiHead指向偶數位置鏈表
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        //e每次指向新遍歷的節點
                        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;
                    }
                    //偶數鏈表的索引變成原索引+原數組容量16
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}


四.Node節點及單鏈表
每個節點存儲key,value,下一個節點,hash值.

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    ...
}


五.treeifyBin() 紅黑樹存儲

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //第一次進來treeifyBin方法就擴容,n是數組默認大小16,小於64,不轉紅黑樹
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        //數組長度擴容爲64時才變成紅黑樹,map內存儲元素個數爲33
        TreeNode<K,V> hd = null, tl = null;
        do {
            //e指向單鏈表第一個結點
        //單鏈表每個節點創建一個TreeNode,填充hash,key,value,next
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            //把單鏈錶轉成紅黑樹
            hd.treeify(tab);
    }
}


六.單鏈錶轉紅黑樹方法

final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next;
        //x指向當前節點,先把左右子樹置null
        x.left = x.right = null;
        //創建紅黑樹根節點
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        else {
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                if ((ph = p.hash) > h)
                    dir = -1;
                else if (ph < h)
                    //下插入的hash值比父節點大,插入到右子樹上
                    dir = 1;
                else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0)
                    dir = tieBreakOrder(k, pk);

                TreeNode<K,V> xp = p;
                //dir = 1時,插入到右子樹.dir = -1是插入到左子樹
                //p根據遍歷的當前節點的值,指向父節點的左子樹或者右子樹
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //插入後做二叉樹的平衡(變色,左右旋)
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}


七.樹節點結構

class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        ...
}


八.紅黑樹的平衡

<K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                                    TreeNode<K,V> x) {
    x.red = true;
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        //只有一個節點,可能是紅色,變成黑色
        if ((xp = x.parent) == null) {
            x.red = false;
            return x;
        }
        //只有一個黑色節點
        else if (!xp.red || (xpp = xp.parent) == null)
            return root;
        if (xp == (xppl = xpp.left)) {
            //參考鏈接1的情況三
            if ((xppr = xpp.right) != null && xppr.red) {
                xppr.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }
            else {
                if (x == xp.right) {
                    //參考鏈接1的情況四
                    root = rotateLeft(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateRight(root, xpp);
                    }
                }
            }
        }
        else {
            if (xppl != null && xppl.red) {
                xppl.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }
            else {
                if (x == xp.left) {
                    root = rotateRight(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}


九.左旋
參考鏈接1的情況四
左旋三步走:右孩子的左子樹掛到父節點的右子樹上,右孩子左旋上移,父節點左旋向左下方下移

static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
    //p是情況四的P節點,r是N節點.即p是連續紅色節點的上面的節點,r是連續紅色的右孩子節點
    TreeNode<K,V> r, pp, rl;
    if (p != null && (r = p.right) != null) {
        //右孩子r的左子樹掛到父節點p的右子樹上
        if ((rl = p.right = r.left) != null)
            //r的左子樹的父節點指向p
            rl.parent = p;
        //r節點左旋上移,即r的父節點指向爺爺節點
        if ((pp = r.parent = p.parent) == null)
            //如果爺爺節點爲空,則r變爲根節點,需要變成黑色
            (root = r).red = false;
        else if (pp.left == p)
            //爺爺節點的左子樹指向r節點
            pp.left = r;
        else
            //爺爺節點的左子樹指向r節點
            pp.right = r;
        //r節點的左子樹指向原來的父節點,即原來的父節點向左下旋轉移動
        r.left = p;
        p.parent = r;
    }
    return root;
}


十.右旋
參考鏈接1的情況五

static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
    //p是情況五的G節點,l是P節點.即p是連續紅色節點的上面紅色節點的父節點,即爺爺節點,l是連續紅色節點的上面的節點
    TreeNode<K,V> l, pp, lr;
    if (p != null && (l = p.left) != null) {
        //爺爺節點的左子樹指向父節點的右子樹,即父親節點的右子樹右旋掛到爺爺節點的左子樹上.
        if ((lr = p.left = l.right) != null)
            //父節點的右子樹指向爺爺節點
            lr.parent = p;
        //爺爺節點如果是null節點,父節點作爲根節點變成黑色
        if ((pp = l.parent = p.parent) == null)
            (root = l).red = false;
        else if (pp.right == p)
            pp.right = l;
        else
            pp.left = l;
        //父節點的右子樹指向爺爺節點
        l.right = p;
        //爺爺節點的父節點指向父節點
        p.parent = l;
    }
    return root;
}

十一.get() 獲取

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章