JDK 8 TreeMap 源碼解析

【本文是爲了梳理知識的總結性文章,總結了一些自認爲相關的重要知識點,只爲鞏固記憶以及技術交流,忘批評指正。其中參考了很多前輩的文章,包括圖片也是引用,如有冒犯,侵刪。】

0 存儲結構

TreeMap 是一個有序的Map,內部按照Key的排序結果來組織。一般如果沒有需要排序的情況下,我們都使用HashMap或者多線程下使用ConcurrentHashMap,因爲TreeMap的插入和刪除的效率沒有前兩者高。但是如果需要有序的Map,那麼就只能選TreeMap了,HashMap是基於Hash散列的,因此是無序的。從底層實現來看,TreeMap 是基於紅黑樹實現的,在學習之前需要先了解紅黑樹的基礎知識,可以參考數據結構之紅黑樹Java實現

1 類定義

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

2 紅黑樹節點Entry結構

// 使用boolean值表示紅黑樹節點顏色
    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; // 節點顏色

        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        public String toString() {
            return key + "=" + value;
        }
    }

3 屬性

    /** 
     * 用於維護此TreeMap中順序的比較器,如果它爲null,則使用key的自然順序。
     */
    private final Comparator<? super K> comparator;
    // 樹的根節點
    private transient Entry<K,V> root;

    /**
     * 樹中節點的個數
     */
    private transient int size = 0;

    /**
     * 樹進行結構性修改的次數
     */
    private transient int modCount = 0;

4 構造函數

主要用於初始化 comparator

    // 默認使用自然排序,插入TreeMap的Key必須實現Comparable接口才能進行比較 
    public TreeMap() {
        comparator = null;
    }

    // 使用指定的比較器,
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 基於現有的Map構造TreeMap
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }

    // 構造一個和SortedMap具有相同順序和元素的新Map
    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) {
        }
    }

5 常用方法

get(Object key)

     public V get(Object key) {
        // 調用getEntry方法進行查找
        Entry<K,V> p = getEntry(key);
        // 不存在返回null
        return (p==null ? null : p.value);
    }

getEntry方法,無論是基於自定義比較器的查找,還是基於自然排序比較器的比較,都是在二叉樹下的查找。當前元素大於目標元素,向左找;當前元素小於目標元素,向右找。

    
    final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        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;
    }

    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);
                if (cmp < 0)   // 當前元素大於目標元素,向左找
                    p = p.left;
                else if (cmp > 0) // 當前元素小於目標元素,向右找
                    p = p.right;
                else
                    return p;
            }
        }
        return null;
    }

put(K key,V value)

插入時先進行查找,看是否存在相同key的節點,存在就直接替換value,不存的話能找到要插入的位置parent, 構造新節點進行插入,然後調用fixAfterInsertion方法進行插入修復,插入修復設計紅黑樹的重新着色和旋轉,詳情見數據結構之紅黑樹Java實現

public V put(K key, V value) {
        // 根節點爲空的情況
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // 類型檢查,有可能爲null

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            // 使用自定義比較器進行查找
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value); // 存在重複的key,直接覆蓋,返回舊值
            } while (t != null);
        }
        else {
            // 使用自然排序比較器進行查找
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                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); // 存在重複的key,直接覆蓋,返回舊值
            } while (t != null);
        }
        // 不存在具有相同Key的節點,新建節點進行插入
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e; // 插入左側
        else
            parent.right = e; // 插入右側
        fixAfterInsertion(e); // 插入修復
        size++;
        modCount++;
        return null;
    }

remove(Object key)

    public V remove(Object key) {
        // 先找到目標節點    
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        // 調用deleteEntry進行刪除   
        deleteEntry(p);
        return oldValue;
    }

deleteEntry刪除方法, 刪除過程,可以簡單分爲4種情況:

  1. 只有一個節點的情況;
  2. 刪除點p的左右子樹都爲空;
  3. 只有一棵子樹非空;
  4. 刪除點p的左右子樹都非空。

對於情況1,直接將根節點root置爲null就可以;

對於情況2,直接將p刪除,將p的parent指向p的指針置爲null;

對於情況3,用非空子樹替代p;

對於情況4,可以用p的後繼s(樹中大於p的最小的那個元素)代替p,然後刪除後繼s。後繼s一定不是左右子樹非空,就可以使用情況1、2、3進行處理。

刪除修復方法fixAfterDeletion詳見,數據結構之紅黑樹Java實現

private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        // 刪除節點p的左右子樹都非空,將P 指向後繼元素
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p); // 尋找後繼
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        // 刪除點p只有一棵子樹非空,從子樹中選出替代元素
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            // 替換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節點
            p.left = p.right = p.parent = null;

            // Fix replacement
            // 插入修復
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // 只有一個節點p的情況
            root = null;
        } else { //  刪除點p的左右子樹都爲空
            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;
            }
        }
    }

successor(Entry<K,V> t)

尋找後繼的方法,對於一棵二叉查找樹,給定節點t,其後繼(樹中比大於t的最小的那個元素)可以通過如下方式找到:

  1. t 的右子樹不空,則t的後繼是其右子樹中最小的那個元素。
  2. t 的右孩子爲空,則t的後繼是其第一個向左走的祖先。
// 尋找節點後繼函數successor()
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
    if (t == null)
        return null;
    else if (t.right != null) {// t的右子樹不空,則t的後繼是其右子樹中最小的那個元素
        Entry<K,V> p = t.right;
        while (p.left != null)
            p = p.left;
        return p;
    } else {//  t的右孩子爲空,則t的後繼是其第一個向左走的祖先
        Entry<K,V> p = t.parent;
        Entry<K,V> ch = t;
        while (p != null && ch == p.right) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

TreeMap_successor.png

firstEntry()

尋找第一個節點,根據二叉查找樹的性質,樹的最做左邊的節點最小。

    public Map.Entry<K,V> firstEntry() {
        return exportEntry(getFirstEntry());
    }
    // 樹的最左邊的是最小值
    final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }
    // 返回簡單不可變entry,如果爲null,則返回null
    static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
        return (e == null) ? null :
            new AbstractMap.SimpleImmutableEntry<>(e);
    }

lastEntry()

返回樹的最大的元素,根據二叉查找樹的性質,樹的最做右邊的節點最小。

    public Map.Entry<K,V> lastEntry() {
        return exportEntry(getLastEntry());
    }
    // 找到根節點的最右邊節點
    final Entry<K,V> getLastEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }

lowerEntry(K key) 

找到剛好小於Key的節點。

    public Map.Entry<K,V> lowerEntry(K key) {
        return exportEntry(getLowerEntry(key));
    }

     final Entry<K,V> getLowerEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp > 0) {
                // key 比當前p.key大
                if (p.right != null)
                    p = p.right; // 向右查找
                else
                    // 比最大的右加點還大,返回最大值
                    return p; 
            } else {
                // key 比當前p.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;
    }

higherEntry(K key)

    public Map.Entry<K,V> higherEntry(K key) {
        return exportEntry(getHigherEntry(key));
    }

     final Entry<K,V> getHigherEntry(K key) {
        Entry<K,V> p = root;
        while (p != null) {
            int cmp = compare(key, p.key);
            if (cmp < 0) {
                // key 比當前p.key小
                if (p.left != null)
                    p = p.left; // 向左查找
                else
                    return p;  // 比最小的左加點還小,返回最小值
            } else {
                // key 比當前p.key大
                if (p.right != null) {
                    p = p.right; // 向右查找
                } else { 
                    // 比最低的右節點大,向上查找第一個右拐的父節點
                    Entry<K,V> parent = p.parent;
                    Entry<K,V> ch = p;
                    while (parent != null && ch == parent.right) {
                        ch = parent;
                        parent = parent.parent;
                    }
                    return parent;
                }
            }
        }
        return null;
    }

......

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