1. 程式人生 > >TreeMap原始碼分析(jdk1.8)

TreeMap原始碼分析(jdk1.8)

TreeMap的基本概念:

  1. TreeMap集合是基於紅黑樹(Red-Black tree)的 NavigableMap實現。該集合最重要的特點就是可排序,該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。這句話是什麼意思呢?就是說TreeMap可以對新增進來的元素進行排序,可以按照預設的排序方式,也可以自己指定排序方式。
  2. 根據上一條,我們要想使用TreeMap儲存並排序我們自定義的類(如User類),那麼必須自己定義比較機制:一種方式是User類去實現Java.lang.Comparable介面,並實現其compareTo()方法。另一種方式是寫一個類(如MyCompatator)去實現java.util.Comparator介面,並實現compare()方法,然後將MyCompatator類例項物件作為TreeMap的構造方法引數進行傳參。
  3. TreeMap的實現是紅黑樹演算法的實現,應該瞭解紅黑樹的基本概念。
    一、紅黑樹簡介
    紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。
    1、每個節點都只能是紅色或者黑色
    2、根節點是黑色
    3、每個葉節點(NIL節點,空節點)是黑色的。
    4、如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
    5、從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
    具體實現參考:http://www.cnblogs.com/fanzhidongyzby/p/3187912.html
    http://blog.csdn.net/eric491179912/article/details/6179908

/************************************TreeMap********************************/
TreeMap的定義如下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap繼承AbstractMap,實現NavigableMap、Cloneable、Serializable三個介面。其中AbstractMap表明TreeMap為一個Map即支援key-value的集合, NavigableMap(更多)則意味著它支援一系列的導航方法,具備針對給定搜尋目標返回最接近匹配項的導航方法 。
TreeMap中同時也包含了如下幾個重要的屬性:

//比較器,因為TreeMap是有序的,通過comparator介面我們可以對TreeMap的內部排序進行精密的控制
        private final Comparator<? super K> comparator;
        //TreeMap紅-黑節點,為TreeMap的內部類
        private transient Entry<K,V> root = null;
        //容器大小
        private transient int size = 0;
        //TreeMap修改次數
        private transient int modCount = 0;
        //紅黑樹的節點顏色--紅色
        private static final boolean RED = false;
        //紅黑樹的節點顏色--黑色
        private static final boolean BLACK = true;
       // 靜態內部類用來表示節點型別
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;     // 鍵
        V value;   // 值
        Entry<K,V> left;    // 指向左子樹的引用(指標)
        Entry<K,V> right;   // 指向右子樹的引用(指標)
        Entry<K,V> parent;  // 指向父節點的引用(指標)
        boolean color = BLACK; // 
    }
}

類構造方法

    public TreeMap() {   // 1,無參構造方法
        comparator = null; // 預設比較機制
    }

    public TreeMap(Comparator<? super K> comparator) { // 2,自定義比較器的構造方法
        this.comparator = comparator;
    }

    public TreeMap(Map<? extends K, ? extends V> m) {  // 3,構造已知Map物件為TreeMap
        comparator = null; // 預設比較機制
        putAll(m);
    }

    public TreeMap(SortedMap<K, ? extends V> m) { // 4,構造已知的SortedMap物件為TreeMap
        comparator = m.comparator(); // 使用已知物件的構造器
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

TreeMap put()方法實現分析
在TreeMap的put()的實現方法中主要分為兩個步驟,第一:構建排序二叉樹,第二:平衡二叉樹。
對於排序二叉樹的建立,其新增節點的過程如下:

  1. 以根節點為初始節點進行檢索。
  2. 與當前節點進行比對,若新增節點值較大,則以當前節點的右子節點作為新的當前節點。否則以當前節點的左子節點作為新的當前節點。
  3. 迴圈遞迴2步驟知道檢索出合適的葉子節點為止。
  4. 將新增節點與3步驟中找到的節點進行比對,如果新增節點較大,則新增為右子節點;否則新增為左子節點。
public V put(K key, V value) {  
           //用t表示二叉樹的當前節點  
            Entry<K,V> t = root;  
            //t為null表示一個空樹,即TreeMap中沒有任何元素,直接插入  
            if (t == null) {  
                //比較key值,空樹還需要比較、排序?
                compare(key, key); // type (and possibly null) check  
                //將新的key-value鍵值對建立為一個Entry節點,並將該節點賦予給root  
                root = new Entry<>(key, value, null);  
                //容器的size = 1,表示TreeMap集合中存在一個元素  
                size = 1;  
                //修改次數 + 1  
                modCount++;  
                return null;  
            }  
            int cmp;     //cmp表示key排序的返回結果  
            Entry<K,V> parent;   //父節點  
            // split comparator and comparable paths  
            Comparator<? super K> cpr = comparator;    //指定的排序演算法  
            //如果cpr不為空,則採用既定的排序演算法進行建立TreeMap集合  
            if (cpr != null) {  
                do {  
                    parent = t;      //parent指向上次迴圈後的t  
                    //比較新增節點的key和當前節點key的大小  
                    cmp = cpr.compare(key, t.key);  
                    //cmp返回值小於0,表示新增節點的key小於當前節點的key,則以當前節點的左子節點作為新的當前節點  
                    if (cmp < 0)  
                        t = t.left;  
                    //cmp返回值大於0,表示新增節點的key大於當前節點的key,則以當前節點的右子節點作為新的當前節點  
                    else if (cmp > 0)  
                        t = t.right;  
                    //cmp返回值等於0,表示兩個key值相等,則新值覆蓋舊值,並返回新值  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //如果cpr為空,則採用預設的排序演算法進行建立TreeMap集合  
            else {  
                if (key == null)     //key值為空丟擲異常  
                    throw new NullPointerException();  
                /* 下面處理過程和上面一樣 */  
                Comparable<? super K> k = (Comparable<? super K>) key;  
                do {  
                    parent = t;  
                    cmp = k.compareTo(t.key);  
                    if (cmp < 0)  
                        t = t.left;  
                    else if (cmp > 0)  
                        t = t.right;  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //將新增節點當做parent的子節點  
            Entry<K,V> e = new Entry<>(key, value, parent);  
            //如果新增節點的key小於parent的key,則當做左子節點  
            if (cmp < 0)  
                parent.left = e;  
          //如果新增節點的key大於parent的key,則當做右子節點  
            else  
                parent.right = e;  
            /*  
             *  上面已經完成了排序二叉樹的的構建,將新增節點插入該樹中的合適位置  
             *  下面fixAfterInsertion()方法就是對這棵樹進行調整、平衡,具體過程參考上面的五種情況  
             */  
            fixAfterInsertion(e);  
            //TreeMap元素數量 + 1  
            size++;  
            //TreeMap容器修改次數 + 1  
            modCount++;  
            return null;  
        }  

上面程式碼中do{}程式碼塊是實現排序二叉樹的核心演算法,通過該演算法我們可以確認新增節點在該樹的正確位置。找到正確位置後將插入即可,這樣做了其實還沒有完成,因為我知道TreeMap的底層實現是紅黑樹,紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的情況,所以下一步就是要進行調整。fixAfterInsertion(e); 調整的過程務必會涉及到紅黑樹的左旋、右旋、著色三個基本操作。程式碼如下:

/** 
     * 新增節點後的修復操作 
     * x 表示新增節點 
     */  
     private void fixAfterInsertion(Entry<K,V> x) {  
            x.color = RED;    //新增節點的顏色為紅色  

            //迴圈 直到 x不是根節點,且x的父節點不為紅色  
            while (x != null && x != root && x.parent.color == RED) {  
                //如果X的父節點(P)是其父節點的父節點(G)的左節點  
                if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {  
                    //獲取X的叔節點(U)  
                    Entry<K,V> y = rightOf(parentOf(parentOf(x)));  
                    //如果X的叔節點(U) 為紅色(情況三)  
                    if (colorOf(y) == RED) {       
                        //將X的父節點(P)設定為黑色  
                        setColor(parentOf(x), BLACK);  
                        //將X的叔節點(U)設定為黑色  
                        setColor(y, BLACK);  
                        //將X的父節點的父節點(G)設定紅色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                    //如果X的叔節點(U為黑色);這裡會存在兩種情況(情況四、情況五)  
                    else {     
                        //如果X節點為其父節點(P)的右子樹,則進行左旋轉(情況四)  
                        if (x == rightOf(parentOf(x))) {  
                            //將X的父節點作為X  
                            x = parentOf(x);  
                            //右旋轉  
                            rotateLeft(x);  
                        }  
                        //(情況五)  
                        //將X的父節點(P)設定為黑色  
                        setColor(parentOf(x), BLACK);  
                        //將X的父節點的父節點(G)設定紅色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父節點的父節點(G)為中心右旋轉  
                        rotateRight(parentOf(parentOf(x)));  
                    }  
                }  
                //如果X的父節點(P)是其父節點的父節點(G)的右節點  
                else {  
                    //獲取X的叔節點(U)  
                    Entry<K,V> y = leftOf(parentOf(parentOf(x)));  
                  //如果X的叔節點(U) 為紅色(情況三)  
                    if (colorOf(y) == RED) {  
                        //將X的父節點(P)設定為黑色  
                        setColor(parentOf(x), BLACK);  
                        //將X的叔節點(U)設定為黑色  
                        setColor(y, BLACK);  
                        //將X的父節點的父節點(G)設定紅色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                  //如果X的叔節點(U為黑色);這裡會存在兩種情況(情況四、情況五)  
                    else {  
                        //如果X節點為其父節點(P)的右子樹,則進行左旋轉(情況四)  
                        if (x == leftOf(parentOf(x))) {  
                            //將X的父節點作為X  
                            x = parentOf(x);  
                           //右旋轉  
                            rotateRight(x);  
                        }  
                        //(情況五)  
                        //將X的父節點(P)設定為黑色  
                        setColor(parentOf(x), BLACK);  
                        //將X的父節點的父節點(G)設定紅色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父節點的父節點(G)為中心右旋轉  
                        rotateLeft(parentOf(parentOf(x)));  
                    }  
                }  
            }  
            //將根節點G強制設定為黑色  
            root.color = BLACK;  
        }  

TreeMap delete()方法:針對於紅黑樹的增加節點而言,刪除顯得更加複雜,使原本就複雜的紅黑樹變得更加複雜。同時刪除節點和增加節點一樣,同樣是找到刪除的節點,刪除之後調整紅黑樹。但是這裡的刪除節點並不是直接刪除,而是通過走了“彎路”通過一種捷徑來刪除的:找到被刪除的節點D的子節點C,用C來替代D,不是直接刪除D,因為D被C替代了,直接刪除C即可。所以這裡就將刪除父節點D的事情轉變為了刪除子節點C的事情,這樣處理就將複雜的刪除事件簡單化了。子節點C的規則是:右分支最左邊,或者 左分支最右邊的。
一幫以getEntry()方法為基礎的獲取元素的方法,其中包括containsKey(),get(),remove()等。

final Entry<K,V> getEntry(Object key) {
        // 如果有自定義比較器物件,就按照自定義規則遍歷二叉樹
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {    // 按照預設比較規則遍歷二叉樹
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

一幫以getFirstEntry(),getLastEntry()為基礎的獲取頭和尾元素的方法,其中包括:firstKey(),lastKey();firstEntry(),lastEntry();pollFirstEntry(),pollLastEntry()

    final Entry<K,V> getFirstEntry() { // 獲取第一個元素也就是最小的元素,一直遍歷左子樹
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

    final Entry<K,V> getLastEntry() { // 獲取最後個元素也就是最大的元素,一直遍歷右子樹
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }