(java 8)HashMap-紅黑樹轉換-源碼解讀


 /**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 當前tab容量為空,或者tab數組大小小於64的情況下,不會進行紅黑樹轉換,使用的 resize()代替
        // resize會增加一倍的容量(在當前的基礎上),可以看看我這邊之前resize()解讀的文章
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize(); 
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
            // 構建節點
                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);
        }
    }
    
     // For treeifyBin
    TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
        return new TreeNode<>(p.hash, p.key, p.value, next);
    }
    
    
   /**
     * Forms tree of the nodes linked from this node.
     * @return root of tree
     * 鏈表轉換成樹
     */
    final void treeify(Node<K,V>[] tab) {
        // 樹的跟節點
        TreeNode<K,V> root = null;
        // 遍歷鏈表,x指向當前節點、next指向下一個節點
        for (TreeNode<K,V> x = this, next; x != null; x = next) {
            // 下一個節點
            next = (TreeNode<K,V>)x.next;
            // 當前節點的左右節點為空
            x.left = x.right = null;
            // 如果還沒有根節點
            if (root == null) {
                // 根節點的父節點為null
                x.parent = null;
                // 根節點為黑色
                x.red = false;
                root = x;
            }
            else {
                // 根節點已存在,獲取當前節點的key
                K k = x.key;
                // 當前節點的hash
                int h = x.hash;
                // 當前節點key所屬的類類型
                Class<?> kc = null;
                // 從根節點開始遍歷
                for (TreeNode<K,V> p = root;;) {
                // dir標識遍歷的方向(左右),ph標識當前循環內當前節點的hash
                    int dir, ph;
                    // 當前循環內當前節點的key
                    K pk = p.key;
                    // 如果當前循環內的節點的hash值大於,當前節點(循環外的x節點)的hash值
                    if ((ph = p.hash) > h)
                    // 當前節點x會放到當前遍歷到的節點的左側
                        dir = -1;
                    else if (ph < h)
                    // 否則是右側
                        dir = 1;
                    // 如果兩個節點的hash相同
                    // 如果當前節點x實現了comparable接口,並且當前遍歷到的節點和當前節點x是相同class的實例,那么通過comparable在比較一次
                    // 如果還是相等,通過tieBreakOrder進行比較,如果identityHashCode還是一樣,最終就會放到左側
                    else if ((kc == null &&
                              (kc = comparableClassFor(k)) == null) ||
                              // 根據comparable進行比較
                             (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);
                    // 保存當前遍歷到的節點
                    TreeNode<K,V> xp = p;
                    /*
                     * 如果dir 小於等於0 :當前鏈表節點一定放置在當前樹節點的左側,但不一定是該樹節點的左孩子,也可能是左孩子的右孩子 或者 更深層次的節點。
                     * 如果dir 大於0 :當前鏈表節點一定放置在當前樹節點的右側,但不一定是該樹節點的右孩子,也可能是右孩子的左孩子 或者 更深層次的節點。
                     * 如果當前樹節點不是葉子節點,那么最終會以當前樹節點的左孩子或者右孩子 為 起始節點  再從GOTO1 處開始 重新尋找自己(當前鏈表節點)的位置
                     * 如果當前樹節點就是葉子節點,那么根據dir的值,就可以把當前鏈表節點掛載到當前樹節點的左或者右側了。
                     * 掛載之后,還需要重新把樹進行平衡。平衡之后,就可以針對下一個鏈表節點進行處理了。
                    */
                    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;
                    }
                }
            }
        }
        // 把所有的鏈表節點都遍歷完之后,最終構造出來的樹可能經歷多個平衡操作,根節點目前到底是鏈表的哪一個節點是不確定的
        // 因為我們要基於樹來做查找,所以就應該把 tab[N] 得到的對象一定根節點對象,而目前只是鏈表的第一個節點對象,所以要做相應的處理。
        moveRootToFront(tab, root);
    }
        
     /**
     * Returns x's Class if it is of the form "class C implements
     * Comparable<C>", else null.
     當x的類型為X,且X直接實現了Comparable接口(比較類型必須為X類本身)時,返回x的運行時類型;否則返回null。
     */
    static Class<?> comparableClassFor(Object x) {
        // 判斷是否是實現了Comparable接口
        if (x instanceof Comparable) {
            Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
            if ((c = x.getClass()) == String.class) // bypass checks
                // 如果是String類型,直接返回String.class
                return c;
                // 判斷是否有直接實現的接口
            if ((ts = c.getGenericInterfaces()) != null) {
                for (int i = 0; i < ts.length; ++i) {
                    if (((t = ts[i]) instanceof ParameterizedType) && // 該接口實現了泛型
                        // 獲取接口不帶參數部分的類型對象,也就是去掉了泛型參數部分的類型對象。
                        ((p = (ParameterizedType)t).getRawType() ==
                        // 該類型是Comparable
                         Comparable.class) &&
                         // 獲取泛型參數數組
                        (as = p.getActualTypeArguments()) != null &&
                        // 只有一個泛型參數,且該實現類型是該類型本身
                        as.length == 1 && as[0] == c) // type arg is c
                        return c;
                }
            }
        }
        return null;
    }

    /**
    * 關於紅黑樹的特性:
    * 性質1.節點是紅色或黑色。
    * 性質2.根節點是黑色。
    * 性質3.每個葉節點(NIL節點,空節點)是黑色的。
    * 性質4.每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
    * 性質5.從任一節點到其每個葉子的路徑上包含的黑色節點數量都相同。
    * root 當前根節,x 新插入的節點,返回重新平衡后的根節點
    */
     static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {
            // 新插入的節點標識為紅色
            x.red = true;
            //  xp:當前節點的父節點、xpp:爺爺節點、xppl:左叔叔節點、xppr:右叔叔節點
            for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
                // 如果父節點為空、說明當前節點就是根節點,那么把當前節點標為黑色,返回當前節點
                if ((xp = x.parent) == null) {//L1
                    x.red = false;
                    return x;
                }
                //  如果父節點為黑色 或者(父節點為紅色 但是 爺爺節點為空)
                //  xp就是根節點時,並且當前樹的節點數小於等於3個時,會出現這種情況
                else if (!xp.red || (xpp = xp.parent) == null) // L2
                    return root;
                // 如果父節點是爺爺節點的左孩子 -- 樹的左子樹
                if (xp == (xppl = xpp.left)) { // L3
                    // 如果右叔叔節點不為空,且是紅色
                    if ((xppr = xpp.right) != null && xppr.red) { // L3_1
                        // 右叔叔置為黑色
                        xppr.red = false;
                        // 父節點置為黑色
                        xp.red = false;
                        // 爺爺節點設置為紅色
                        xpp.red = true;
                        // 下一輪循環,當前節點變為爺爺節點開始
                        x = xpp;
                    }
                    // 如果右叔叔為空,或者為黑色
                    else {// L3_2
                        // 如果當前節點是父節點的右孩子
                        if (x == xp.right) { //L3_2_1
                            // 父節點左旋
                            root = rotateLeft(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // 如果父節點不為空
                        if (xp != null) { // L3_2_2
                            // 父節點 置為黑色
                            xp.red = false;
                            // 爺爺節點不為空
                            if (xpp != null) {
                                // 爺爺節點置為 紅色
                                xpp.red = true;
                                // 爺爺節點右旋
                                root = rotateRight(root, xpp);
                            }
                        }
                    }
                }
                // 如果父節點是爺爺節點的右孩子 -- 樹的右子樹
                else { // L4
                    // 如果左叔叔是紅色,和上面的"右叔叔節點不為空,且是紅色"的情況類似
                    if (xppl != null && xppl.red) { // L4_1
                        // 左叔叔置為 黑色 
                        xppl.red = false;
                        // 父節點置為黑色
                        xp.red = false;
                        // 爺爺置為紅色
                        xpp.red = true;
                        // 下一輪循環,當前節點變為爺爺節點開始
                        x = xpp;
                    }
                    // 如果左叔叔為空或者是黑色 
                    else { // L4_2
                        //  如果當前節點是個左孩子
                        if (x == xp.left) {  // L4_2_1
                            // 針對父節點做右旋
                            root = rotateRight(root, x = xp);
                            xpp = (xp = x.parent) == null ? null : xp.parent;
                        }
                        // 如果父節點不為空
                        if (xp != null) { //L4_2_4
                            // 父節點置為黑色
                            xp.red = false;
                            // 如果爺爺節點不為空
                            if (xpp != null) {
                                // 爺爺節點置為紅色
                                xpp.red = true;
                                // 針對爺爺節點做左旋
                                root = rotateLeft(root, xpp);
                            }
                        }
                    }
                }
            }
        }
    
/**
 * 節點左旋
 * root 根節點
 * p 要左旋的節點
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
    TreeNode<K,V> r, pp, rl;
    if (p != null && (r = p.right) != null) { // 要左旋的節點以及要左旋的節點的右孩子不為空
        if ((rl = p.right = r.left) != null) // 要左旋的節點的右孩子的左節點 賦給 要左旋的節點的右孩子 節點為:rl
            rl.parent = p; // 設置rl和要左旋的節點的父子關系【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】
 
        // 將要左旋的節點的右孩子的父節點  指向 要左旋的節點的父節點,相當於右孩子提升了一層,
        // 此時如果父節點為空, 說明r 已經是頂層節點了,應該作為root 並且標為黑色
        if ((pp = r.parent = p.parent) == null) 
            (root = r).red = false;
        else if (pp.left == p) // 如果父節點不為空 並且 要左旋的節點是個左孩子
            pp.left = r; // 設置r和父節點的父子關系【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
        else // 要左旋的節點是個右孩子
            pp.right = r; 
        r.left = p; // 要左旋的節點  作為 他的右孩子的左節點
        p.parent = r; // 要左旋的節點的右孩子  作為  他的父節點
    }
    return root; // 返回根節點
}
 
/**
 * 節點右旋
 * root 根節點
 * p 要右旋的節點
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
    TreeNode<K,V> l, pp, lr;
    if (p != null && (l = p.left) != null) { // 要右旋的節點不為空以及要右旋的節點的左孩子不為空
        if ((lr = p.left = l.right) != null) // 要右旋的節點的左孩子的右節點 賦給 要右旋節點的左孩子 節點為:lr
            lr.parent = p; // 設置lr和要右旋的節點的父子關系【之前只是爹認了孩子,孩子還沒有答應,這一步孩子也認了爹】
 
        // 將要右旋的節點的左孩子的父節點  指向 要右旋的節點的父節點,相當於左孩子提升了一層,
        // 此時如果父節點為空, 說明l 已經是頂層節點了,應該作為root 並且標為黑色
        if ((pp = l.parent = p.parent) == null) 
            (root = l).red = false;
        else if (pp.right == p) // 如果父節點不為空 並且 要右旋的節點是個右孩子
            pp.right = l; // 設置l和父節點的父子關系【之前只是孩子認了爹,爹還沒有答應,這一步爹也認了孩子】
        else // 要右旋的節點是個左孩子
            pp.left = l; // 同上
        l.right = p; // 要右旋的節點 作為 他左孩子的右節點
        p.parent = l; // 要右旋的節點的父節點 指向 他的左孩子
    }
    return root;
}

紅黑樹轉換圖例:

1、無旋轉

 

 2、有旋轉

 

 

 

 

參考:https://blog.csdn.net/qpzkobe/article/details/79533237

參考:https://blog.csdn.net/weixin_42340670/article/details/80531795

參考:https://blog.csdn.net/weixin_42340670/article/details/80531795


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM