【集合框架】JDK1.8原始碼分析之TreeMap(五)

leesf發表於2016-03-08

一、前言

  當我們需要把插入的元素進行排序的時候,就是時候考慮TreeMap了,從名字上來看,TreeMap肯定是和樹是脫不了干係的,它是一個排序了的Map,下面我們來著重分析其原始碼,理解其底層如何實現排序功能。下面,開始分析。

二、TreeMap示例

import java.util.TreeMap;
import java.util.Map;

public class TreeMapTest {
    public static void main(String[] args) {
        Map<String, String> maps = new TreeMap<String, String>();
        maps.put("aa", "aa");
        maps.put("cc", "cc");
        maps.put("bb", "bb");
        
        for (Map.Entry<String, String> entry : maps.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }
    }
}
View Code

  執行結果:

  aa : aa
  bb : bb
  cc : cc

  說明:從輸出結果可以看到TreeMap對插入的元素進行了排序。

三、TreeMap資料結構

  TreeMap底層使用的資料結構是紅黑樹,有印象的的讀者,應該知道我們在分析HashMap的時候就已經接觸到了紅黑樹結構,只是沒有對紅黑樹進行詳細的分析,現在,筆者也並不打算對紅黑樹做太過仔細的分析,因為筆者之後會出資料結構的專題(先挖個坑),到時候再來一睹各種資料結構的風采。

  

  說明:上圖為典型的紅黑樹結構,效率很高,具體的細節問題,我們以後詳談。

四、TreeMap原始碼分析

  4.1 類的繼承關係  

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

  說明:繼承了抽象類AbstractMap,AbstractMap實現了Map介面,實現了部分方法。不能進行例項化,實現了NavigableMap,Cloneable,Serializable介面,其中NavigableMap是繼承自SortedMap的介面,定義了一系列規範。

  4.2 類的屬性 

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 比較器,用於控制Map中的元素順序
    private final Comparator<? super K> comparator;
    // 根節點
    private transient Entry<K,V> root;
    // 樹中結點個數
    private transient int size = 0;
    // 對樹進行結構性修改的次數
    private transient int modCount = 0;
}
View Code

  說明:重點是比較器Comparator,此介面實現了對插入元素進行排序。

  4.3 類的建構函式

  1. TreeMap()型建構函式

public TreeMap() {
        // 無使用者自定義比較器
        comparator = null;
    }
View Code

  2. TreeMap(Comparator<? super K>)型建構函式  

// 自定義了比較器
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
View Code

  說明:使用者自定義了比較器,可以按照使用者的邏輯進行比較,確定元素的訪問順序。

  3. TreeMap(Map<? extends K, ? extends V>)型建構函式  

// 從已有map中構造TreeMap
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
View Code

  說明:根據已有的Map構造TreeMap。

  4. TreeMap(SortedMap<K, ? extends V>)型建構函式  

// 從SortedMap中構造TreeMap,有比較器
    public TreeMap(SortedMap<K, ? extends V> m) {
        comparator = m.comparator();
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }
View Code

  說明:傳入SortedMap型引數,實現SortedMap介面的類都會實現comparator方法,用於返回比較器。

  4.4 核心函式分析

  1. put函式

public V put(K key, V value) {
        // 記錄根節點
        Entry<K,V> t = root;
        // 根節點為空
        if (t == null) {
            // 比較key
            compare(key, key); // type (and possibly null) check
            // 新生根節點
            root = new Entry<>(key, value, null);
            // 大小加1
            size = 1;
            // 修改次數加1
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // 獲取比較器
        Comparator<? super K> cpr = comparator;
        // 比較器不為空
        if (cpr != null) {
            // 找到元素合適的插入位置
            do {
                // parent賦值
                parent = t;
                // 比較key與元素的key值,在Comparator類的compare方法中可以實現我們自己的比較邏輯
                cmp = cpr.compare(key, t.key);
                // 小於結點key值,向左子樹查詢
                if (cmp < 0)
                    t = t.left;
                // 大於結點key值,向右子樹查詢
                else if (cmp > 0)
                    t = t.right;
                // 表示相等,直接更新結點的值
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 比較器為空
        else {
            // key為空,丟擲異常
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                // 取得K實現的比較器
                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);
        }
        // 新生一個結點
        Entry<K,V> e = new Entry<>(key, value, parent);
        // 根據比較結果決定存為左結點或右結點
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 插入後進行修正
        fixAfterInsertion(e);
        // 大小加1
        size++;
        // 進行了結構性修改
        modCount++;
        return null;
    }
View Code

  說明:插入一個元素時,若使用者自定義比較器,則會按照使用者自定義的邏輯確定元素的插入位置,否則,將會使用K自身實現的比較器確定插入位置。

  2. getEntry函式  

final Entry<K,V> getEntry(Object key) {
        // 判斷比較器是否為空
        if (comparator != null)
            // 根據自定義的比較器來返回結果
            return getEntryUsingComparator(key);
        // 比較器為空
        // key為空,丟擲異常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            // 取得K自身實現了比較介面
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        // 根據Comparable介面的compareTo函式來查詢元素
        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;
    }
View Code

  說明:當我們呼叫get函式時,實際上是委託getEntry函式獲取元素,對於使用者自定義實現的Comparator比較器而言,是使用getEntryUsingComparator函式來完成獲取邏輯。

  具體程式碼如下

final Entry<K,V> getEntryUsingComparator(Object key) {
        @SuppressWarnings("unchecked")
            // 向下轉型
            K k = (K) key;
        // 取得比較器
        Comparator<? super K> cpr = comparator;
        // 比較器不為空
        if (cpr != null) {
            Entry<K,V> p = root;
            // 開始遍歷樹節點找到對應的結點
            while (p != null) {
                int cmp = cpr.compare(k, p.key);
                // 小於結點key值,向左子樹查詢
                if (cmp < 0)
                    p = p.left;
                // 大於結點key值,向右子樹查詢
                else if (cmp > 0)
                    p = p.right;
                // 相等,找到,直接返回
                else
                    return p;
            }
        }
        return null;
    }
View Code

  說明:會根據使用者定義在compare函式裡面的邏輯進行元素的查詢。

  3. deleteEntry函式 

private void deleteEntry(Entry<K,V> p) {
        // 結構性修改
        modCount++;
        // 大小減1
        size--;
        // p的左右子結點均不為空
        if (p.left != null && p.right != null) {
            // 找到p結點的後繼
            Entry<K,V> s = successor(p);
            // 將p的值用其後繼結點的key-value替換,並且用s指向其後繼
            p.key = s.key;
            p.value = s.value;
            p = s;
        } 

        // 開始進行修正,具體的修正過程我們會在之後的資料結構專區進行講解
        // 現在可以看成是為了保持紅黑樹的特性,提高效能
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }
View Code

  說明:deleteEntry函式會在remove函式中被呼叫,它完成了移除元素的主要工作,刪除該結點後會對紅黑樹進行修正,此部分內容以後會詳細講解,同時,在此函式中需要呼叫successor函式,即找到該結點的後繼結點。具體函式程式碼如下 

// 找到後繼
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        // t為null,直接返回null
        if (t == null)
            return null;
        // 右孩子不為空
        else if (t.right != null) {
            // 找到右孩子的最底層的左孩子,返回
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else { // 右孩子為空
            // 儲存t的父節點
            Entry<K,V> p = t.parent;
            // 儲存t結點
            Entry<K,V> ch = t;
            // 進行回溯,找到後繼,直到p == null || ch != p.right
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
View Code

  說明:當結點的右子樹為空的時候,進行回溯可以找到該結點的後繼結點。

五、問題擴充套件

  1. 如何找到小於指定結點的最大結點?參考getLowerEntry函式原始碼

  2. 如何找到大於指定結點的最小結點?參考getHigherEntry函式原始碼 

  對getLowerEntry原始碼分析如下 

final Entry<K,V> getLowerEntry(K key) {
        // 儲存根節點
        Entry<K,V> p = root;
        // 根節點不為空
        while (p != null) {
            // 比較該key與節點的key
            int cmp = compare(key, p.key);
            if (cmp > 0) { // 如果該key大於結點的key
                // 如果結點的右子樹不為空,與該結點右結點進行比較
                if (p.right != null)
                    p = p.right;
                else // 右子樹為空,則直接返回結點;因為此時已經沒有比該結點key更大的結點了(右子樹為空)
                    return p;
            } else { // 如果該key小於等於結點的key
                // 結點的左子樹不為空,與該結點的左結點進行比較
                if (p.left != null) { 
                    p = p.left;
                } else { // 結點的左子樹不為空,則開始進行回溯
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.left) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            }
        }
        return null;
    }
View Code

  流程圖如下:

    

  getHigherEntry則可以以此類推。

六、總結

  由TreeMap我們可以知道其底層的資料結構為紅黑樹,並且可以使用使用者自定義的比較器來實現比較邏輯。對於其核心函式的分析就到此為止了,謝謝各位園友的觀看~

 

  

相關文章