TreeMap源碼解析

前言

  1. TreeMap實現了SotredMap接口,它是有序的集合。而且由紅黑樹實現的,每個key-value都作爲一個紅黑樹的節點。如果在調用TreeMap的構造函數時沒有指定比較器,則根據key執行自然排序。
  2. 紅黑樹:
    紅黑樹是二叉樹的一種優化,保留了二叉樹的特性包含一個根節點和兩個子節點,其中 【左子節點 < 根節點 < 右子節點】,又增加了每個節點的顏色紅和黑,通過二叉樹的左右旋轉保證其接近最優二叉樹。想要了解更多紅黑樹的知識,可以看一下下面的博文:
    1. 教你初步瞭解紅黑樹: https://blog.csdn.net/v_JULY_v/article/details/6105630
    2. 紅黑樹算法的實現與剖析: https://blog.csdn.net/v_JULY_v/article/details/6109153
    3. 紅黑樹模擬網站: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
    4. 根據紅黑樹模擬網站,生成的一個簡單的紅黑樹示意圖:
      在這裏插入圖片描述

TreeMap簡單示例:

public class Test {

    public static void main(String[] args) throws ParseException {
        Map treeMap =  new TreeMap<Integer,String>();
        treeMap.put(1,"11");
        treeMap.put(2,"22");
        treeMap.put(3,"33");
        treeMap.put(4,"44");

        Iterator iter = treeMap.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
        }
    }
}

打印結果:

key = 1;value = 11
key = 2;value = 22
key = 3;value = 33
key = 4;value = 44

TreeMap參數定義 和 初始化方法:

  1. 我們先開看一下 TreeMap 參數定義, 比較重要的有有三個:
    comparator: 對比器,對比key之間的大小。
    root:根節點,整個紅黑樹的根,例如上面的圖 d 節點就是根節點
    size:集合大小

  2. TreeMap 的初始化方法也有三個:
    TreeMap():其中comparator=null,在實際運用中會取 key 中定義的 comparator ,例如String類型中就內置了 CaseInsensitiveComparator 作爲 Comparator 的實現類。
    TreeMap(Comparator<? super K> comparator):帶有對比器的初始化方法。
    TreeMap(SortedMap<K, ? extends V> m): 帶有集合m 的初始化方法,沿用m的對比器, 並將m中鍵值對放入到TreeMap中

public class TreeMap<K,V>
        extends AbstractMap<K,V>
        implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 對比器
    private final Comparator<? super K> comparator;

    // 根節點
    private transient java.util.TreeMap.Entry<K,V> root;

    // 集合大小
    private transient int size = 0;

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

    // 初始化
    public TreeMap() {
        comparator = null;
    }

    // 帶有 對比器 的初始化
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

    // 初始化 並將 m 全部放置到 TreeMap 中
    public TreeMap(Map<? extends K, ? extends V> m) {
        comparator = null;
        putAll(m);
    }
	..... 
}

TreeMap 中保存 key-value 的 Entry

  1. Entry 中包含了 key、value、left、right、parent、color
    left:表示當前節點的左子節點
    right:表示當前節點的右子節點
    parent: 自己的父節點
  2. 代碼如下:
 	// 鍵值對
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key; // key 值
        V value; // value 值
        java.util.TreeMap.Entry<K,V> left;  // 左邊子的節點
        java.util.TreeMap.Entry<K,V> right; // 右邊子的節點
        java.util.TreeMap.Entry<K,V> parent; // 父節點
        boolean color = BLACK; // 默認節點顏色 黑

        // 初始化 Entry
        Entry(K key, V value, java.util.TreeMap.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;
        }
    }

put 方法

TreeMap 中定義了很多方法,其中最關鍵的是 put(K key, V value) 方法,該方法將 key-value 包裝成了一個 Entry,並通過 Comparator 在紅黑樹中找到指定的位置將其放置其中,最後通過 fixAfterInsertion 實現紅黑樹的修復,完成新增操作,下面看源碼:


// 添加一個數據
    public V put(K key, V value) {
        // 獲取到紅黑樹的根節點
        java.util.TreeMap.Entry<K,V> t = root;
        // 根節點爲null, TreeMap中沒有任何數據
        if (t == null) {
            // 類型檢查(可能爲空)
            compare(key, key);
            // 將當前的 key,value 生成根節點Entry
            root = new java.util.TreeMap.Entry<>(key, value, null);
            size = 1;
            // 操作次數 +1
            modCount++;
            return null;
        }
        int cmp;
        // 定義父節點
        java.util.TreeMap.Entry<K,V> parent;
        // 比較器
        Comparator<? super K> cpr = comparator;
        // 用戶傳入的比較器不爲空
        if (cpr != null) {
            // 循環查找 key在二叉樹對應的位置【即找到一個父節點】
            do {
                parent = t;
                // key與當前節點的key進行對比
                cmp = cpr.compare(key, t.key);
                // cmp < 0 找到當前節點的左子節點再去對比
                if (cmp < 0)
                    t = t.left;
                // cmp > 0 找到當前節點的右子節點再去對比
                else if (cmp > 0)
                    t = t.right;
                // cmp = 0 當前節點即是key的節點,用Value值替換掉舊值
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 使用 key 數據類型中默認的比較器,後面邏輯相同
        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);
            } while (t != null);
        }
        // 上面的代碼如果直接return了表示。TreeMap中原來存在 key=參數key的節點,直接覆蓋返回

        // 下面的代碼表示紅黑樹中不存在Key,需要爲其創建節點, parent上面循環得到的二叉樹的葉子及誒到哪
        java.util.TreeMap.Entry<K,V> e = new java.util.TreeMap.Entry<>(key, value, parent);

        // cmp < 0 表示當前key生成的Entry是parent的左節點
        if (cmp < 0)
            parent.left = e;
        // cmp > 0 表示當前key生成的Entry是parent的右節點
        else
            parent.right = e;
        // 插入新的節點後,紅黑樹進行修復【節點顏色變化,左右旋等】,保證其扔就滿足紅黑樹的特性
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
	
	**
     * 插入節點後,紅黑樹進行自我修復,保證其仍然滿足紅黑樹的要求
     * 由於技術有限,暫時沒有進行解讀
     * @param x
     */
    private void fixAfterInsertion(java.util.TreeMap.Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                java.util.TreeMap.Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                java.util.TreeMap.Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

如果大家瞭解二叉樹的話【要是懂得紅黑樹,就更完美了】,上面 put 方法也很容易理解了,每一行代碼的意思在已經標明註釋了,這裏不多說了。關於方法最後 fixAfterInsertion 調整紅黑樹結構的方法,由於本人對紅黑樹理解不夠透徹,也就沒有解讀。

get 方法

不論是 get 還是 put 方法,都是通過根節點 和 Comparable 對紅黑樹進行查找,最後實現功能,代碼如下:

	// 通過 key 獲取到Value的值
    public V get(Object key) {
        java.util.TreeMap.Entry<K,V> p = getEntry(key);
        return (p==null ? null : p.value);
    }
	
	// 跟 key 值找到對應的 Entry
    final java.util.TreeMap.Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        // TreeMap 中key 不可以爲空
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
        // 得到TreeMap 中紅黑樹的 根節點
        java.util.TreeMap.Entry<K,V> p = root;
        // 循環遍歷
        while (p != null) {
            // key和當前節點的key比較
            int cmp = k.compareTo(p.key);
            // 根據二叉樹的原理,
            // cmp < 0 找左節點
            if (cmp < 0)
                p = p.left;
            // cmp > 0 找右節點
            else if (cmp > 0)
                p = p.right;
            // 當前節點就是要查找的節點
            else
                return p;
        }
        return null;
    }

TreeMap 中的 comparator 介紹:

  1. 我們通過上面的 put 和 get 方法,我們發現 comparator 在其中起到了查詢導向的作用,那麼 comparator 究竟是怎麼實現的,我們先來看一下 Comparator 接口:
public interface Comparator<T> {
    /**
     * 比較對象
     * @param o1
     * @param o2
     * @return
     */
    int compare(T o1, T o2);
}

我們在實際工作中可以編寫一個實現類,實現Comparator 並完成 compare 接口,例如我們實現一個Integer 倒序的 TreeMap:


public class Test {

    public static void main(String[] args) throws ParseException {
        Map treeMap =  new TreeMap<String,String>(new IntegerComparator());
        treeMap.put(1,"11");
        treeMap.put(2,"22");
        treeMap.put(3,"33");
        treeMap.put(4,"44");
        Iterator iter = treeMap.entrySet().iterator();
        while(iter.hasNext()) {
            Map.Entry entry = (Map.Entry)iter.next();
            System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
        }
    }
}

class IntegerComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        return -((Integer) o1 - (Integer)o2);
    }
}

打印結果:

key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11

TreeMap的遍歷方式:

  1. 第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】
  2. 第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦】
  3. 第三種: 根據value()獲取TreeMap的“值”的集合。

代碼示例如下:


public class Test {

    public static void main(String[] args) throws ParseException {
        Map treeMap =  new TreeMap<Integer,String>(new IntegerComparator());
        treeMap.put(1,"11");
        treeMap.put(2,"22");
        treeMap.put(3,"33");
        treeMap.put(4,"44");

        System.out.println(" 第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】 ");
        Iterator iter1 = treeMap.entrySet().iterator();
        while(iter1.hasNext()) {
            Map.Entry entry = (Map.Entry)iter1.next();
            System.out.println("key = "+entry.getKey()+ ";value = " + entry.getValue());
        }

        System.out.println(" 第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦,因爲會遍歷兩次Map】 ");
        Integer key = null;
        Iterator iter2 = treeMap.keySet().iterator();
        while(iter2.hasNext()) {
            key = (Integer) iter2.next();
            System.out.println("key = "+key+ ";value = " + treeMap.get(key));
        }

        System.out.println(" 第三種: 根據value()獲取TreeMap的“值”的集合。 ");
        String value = null;
        Iterator iter3 = treeMap.values().iterator();
        while(iter3.hasNext()) {
            value = (String) iter3.next();
            System.out.println( "value = " + value);
        }
    }
}

class IntegerComparator implements Comparator{

    @Override
    public int compare(Object o1, Object o2) {
        return -((Integer) o1 - (Integer)o2);
    }
}

打印結果:


 第一種:根據entrySet()獲取TreeMap的“鍵值對”的Set集合 【推薦】 
key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11
 第二種: 根據keySet()獲取TreeMap的“鍵”的Set集合, 然後再根據key取得Value值 【不推薦,因爲會遍歷兩次Map】 
key = 4;value = 44
key = 3;value = 33
key = 2;value = 22
key = 1;value = 11
 第三種: 根據value()獲取TreeMap的“值”的集合。 
value = 44
value = 33
value = 22
value = 11

總結: TreeMap相對比較簡單,但是首先要知道什麼是二叉樹,以及對紅黑樹有點點認識,剩下的細心點閱讀以下源碼即可。

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