jdk1.8 HashMap紅黑樹操作詳解-putTreeVal()


以前也看過hashMap源碼不過是看的jdk1.7的,由於時間問題看的也不是太深入,只是大概的了解了一下他的基本原理;這幾天通過假期的時間就對jdk1.8的hashMap深入了解了下,相信大家都是對紅黑樹和hashMap的擴容機制resize()比較感興趣,紅黑樹也是jdk1.8對hashMap新加的一種數據結構,單純的樹形結構挺簡單的,不過紅黑樹是一種自動保持平衡的樹形結構,那就比較復雜了,可以通過我另一個博客可以先看一下紅黑樹的原理再看hashMap中的紅黑樹就簡單多了連接<<<<<,hashMap的擴容機制resize()連接<<<<,廢話不多說看代碼(一些解釋都在代碼中)

 

 /**
     * 插入樹形類型的元素
     * Tree version of putVal.
     * 
     * 操作:將插入節點的hash值與每個節點的hash值進行比較,
     * 如果是小於那下一次插入節點就與當前節點的左子節點比,反之則與右子節點比,
     * 直到當前節點的(左或右)子節點為null,將其插入;
     * 每當插入一次節點都會調用一次方法balanceInsertion(root, x)將紅黑樹進行一個平衡操作
     */
    final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                                   int h, K k, V v) {
        Class<?> kc = null;
        boolean searched = false;
        //獲取樹的根節點
        TreeNode<K,V> root = (parent != null) ? root() : this;
        for (TreeNode<K,V> p = root;;) {
            int dir, ph; K pk;
            //h:插入節點的hash值
            //p:遍歷到的當前節點
            if ((ph = p.hash) > h)
                dir = -1;
            else if (ph < h)
                dir = 1;
            else if ((pk = p.key) == k || (k != null && k.equals(pk)))
                return p;
            else if ((kc == null &&
                      (kc = comparableClassFor(k)) == null) ||
                     (dir = compareComparables(kc, k, pk)) == 0) {
                if (!searched) {
                    TreeNode<K,V> q, ch;
                    searched = true;
                    if (((ch = p.left) != null &&
                         (q = ch.find(h, k, kc)) != null) ||
                        ((ch = p.right) != null &&
                         (q = ch.find(h, k, kc)) != null))
                        return q;
                }
                dir = tieBreakOrder(k, pk);
            }

            TreeNode<K,V> xp = p;
            if ((p = (dir <= 0) ? p.left : p.right) == null) {
                Node<K,V> xpn = xp.next;
                TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
                if (dir <= 0)
                    xp.left = x;
                else
                    xp.right = x;
                xp.next = x;
                x.parent = x.prev = xp;
                if (xpn != null)
                    ((TreeNode<K,V>)xpn).prev = x;
                //balanceInsertion(root, x):此方法是對紅黑樹進行平衡操作
                moveRootToFront(tab, balanceInsertion(root, x));
                return null;
            }
        }
    }

    /**
     * 插入元素 平衡紅黑樹的方法 
     * 
     * 注:不管遇到一下三種情況任意一種情況就會進行平衡調整 ,反之不需要;如果x節點是第1種情況,必然會經歷2,3;如果x節點是第3種情況不會經歷1,2;
     * 
     * 1. 插入節點的父節點和其叔叔節點(祖父節點的另一個子節點)均為紅色的;
     * 
     * 2. 插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的右子節點;
     * 
     * 3. 插入節點的父節點是紅色,叔叔節點是黑色,且插入節點是其父節點的左子節點。
     * 
     * @param root
     * @param x
     * @return
     */
    static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x) {
        //將插入的節點塗成紅色
        x.red = true;
        //此時x節點是剛插入的節點
        // 這些變量名不是作者隨便定義的都是有意義的。
        // xp:x parent,代表x的父節點。
        // xpp:x parent parent,代表x的祖父節點
        // xppl:x parent parent left,代表x的祖父的左節點。
        // xppr:x parent parent right,代表x的祖父的右節點。
        for (TreeNode<K, V> xp, xpp, xppl, xppr;;) {
            //如果x.parent==null證明x是根節點,並將x(根節點)返回;平衡完畢。
            if ((xp = x.parent) == null) {
                //將根節點塗成黑色
                x.red = false;
                return x;
            //只要進入此if就不會滿足注釋中3條件的任意一個,直接將root返回;平衡完畢。
            } else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            //若父節點是祖父節點的左子節點,與下面的完全相反,本質是一樣的
            if (xp == (xppl = xpp.left)) {
                //x節點的祖父節點的右子節點(叔叔節點)不為null且是紅色,父節點必然也是紅色,此時滿足第1種情況。
                //操作:1.將祖父節點的右子節點(叔叔節點)、父節點塗為黑色
                //     2.將祖父節點塗為紅色
                //     3.將祖父節點賦給x(參照節點的變更)
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                //進入else 說明已經是第2或第3種情況了    
                } else {
                    //第2種情況
                    // 操作:1.標記節點變為x。
                    //        2.左旋
                    //      3.x的父節點、x的祖父節點隨之變化
                    if (x == xp.right) {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    //第3種情況 
                    //操作 1.將父節點塗黑
                    //    2.祖父節點塗紅
                    //    3.右旋
                    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);
                        }
                    }
                }
            }
        }
    }

 左旋和右旋只通過代碼說,沒有圖聽不明白,必須通過上邊說的紅黑樹原理的那篇博客結合代碼才容易看明白,先上一個那篇博客中的左旋和右旋的動態圖先了解一下

 左旋有個很萌萌噠的動態示意圖,可以方便理解:

 

 右旋也有個很萌萌噠的動態示意圖,可以方便理解

hashMap的代碼我就不貼了大家可以自己看一下

 


免責聲明!

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



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