前言
本文不打算延續前幾篇的風格(對所有的源碼加入注釋),因為要理解透TreeMap的所有源碼,對博主來說,確實需要耗費大量的時間和經曆,目前看來不大可能有這麼多時間的投入,故這裡意在通過於閱讀源碼對TreeMap有個宏觀上的把握,並就其中一些方法的實現做比較深入的分析。
紅/黑樹狀結構簡介
TreeMap是基於紅/黑樹狀結構實現的,這裡只對紅/黑樹狀結構做個簡單的介紹,紅/黑樹狀結構是一種特殊的二叉排序樹,關於二叉排序樹,參見:http://blog.csdn.net/ns_code/article/details/19823463,紅/黑樹狀結構通過一些限制,使其不會出現二叉樹排序樹中極端的一邊倒的情況,相對二叉排序樹而言,這自然提高了查詢的效率。
二叉排序樹的基本性質如下:
1、每個節點都只能是紅色或者黑色
2、根節點是黑色
3、每個分葉節點(NIL節點,空節點)是黑色的。
4、如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
5、從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
正是這些性質的限制,使得紅/黑樹狀結構中任一節點到其子孫葉子節點的最長路徑不會長於最短路徑的2倍,因此它是一種接近平衡的二叉樹。
本欄目更多精彩內容:http://www.bianceng.cn/Programming/Java/
說到紅/黑樹狀結構,自然不免要和AVL樹對比一番。相比較而言,AVL樹是嚴格的平衡二叉樹,而紅/黑樹狀結構不算嚴格意義上的平衡二叉樹,只是接近平衡,不會讓樹的高度如BST極端情況那樣等於節點的個數。其實能用到紅/黑樹狀結構的地方,也都可以用AVL樹來實現,但紅/黑樹狀結構的應用卻非常廣泛,而AVL樹則很少被使用。在執行插入、刪除操作時,AVL樹需要調整的次數一般要比紅/黑樹狀結構多(紅/黑樹狀結構的旋轉調整最多隻需三次),效率相對較低,且紅/黑樹狀結構的統計效能較AVL樹要好,當然AVL樹在查詢效率上可能更勝一籌,但實際上也高不了多少。
紅/黑樹狀結構的插入刪除操作很簡單,就是單純的二叉排序樹的插入刪除操作。紅/黑樹狀結構被認為比較變態的地方自然在於插入刪除後對紅/黑樹狀結構的調整操作(旋轉和著色),主要是情況分的很多,限於篇幅及博主的熟悉程度優先,這裡不打算詳細介紹插入刪除後調整紅/黑樹狀結構的各種情況及其實現,我們有個宏觀上的瞭解即可,如須詳細瞭解,參見演算法導論或一些相關的資料。
TreeMap源碼剖析
儲存結構
TreeMap的排序是基於對key的排序實現的,它的每一個Entry代表紅/黑樹狀結構的一個節點,Entry的資料結構如下:
static final class Entry<K,V> implements Map.Entry<K,V> { // 鍵 K key; // 值 V value; // 左孩子 Entry<K,V> left = null; // 右孩子 Entry<K,V> right = null; // 父節點 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; } 。。。。。 }
構造方法
先來看下TreeMap的構造方法。TreeMap一共有4個構造方法。
1、無參構造方法
public TreeMap() { comparator = null; }
採用無參構造方法,不指定比較子,這時候,排序的實現要依賴key.compareTo()方法,因此key必須實現Comparable介面,並覆寫其中的compareTo方法。
2、帶有比較子的構造方法
public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
採用帶比較子的構造方法,這時候,排序依賴該比較子,key可以不用實現Comparable介面。
3、帶Map的構造方法
public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
該構造方法同樣不指定比較子,調用putAll方法將Map中的所有元素加入到TreeMap中。putAll的源碼如下:
// 將map中的全部節點添加到TreeMap中 public void putAll(Map<? extends K, ? extends V> map) { // 擷取map的大小 int mapSize = map.size(); // 如果TreeMap的大小是0,且map的大小不是0,且map是已排序的“key-value對” if (size==0 && mapSize!=0 && map instanceof SortedMap) { Comparator c = ((SortedMap)map).comparator(); // 如果TreeMap和map的比較子相等; // 則將map的元素全部拷貝到TreeMap中,然後返回! if (c == comparator || (c != null && c.equals(comparator))) { ++modCount; try { buildFromSorted(mapSize, map.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } return; } } // 調用AbstractMap中的putAll(); // AbstractMap中的putAll()又會調用到TreeMap的put() super.putAll(map); }
顯然,如果Map裡的元素是排好序的,就調用buildFromSorted方法來拷貝Map中的元素,這在下一個構造方法中會重點提及,而如果Map中的元素不是排好序的,就調用AbstractMap的putAll(map)方法,該方法源碼如下:
public void putAll(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); }
很明顯它是將Map中的元素一個個put(插入)到TreeMap中的,主要因為Map中的元素是無序存放的,因此要一個個插入到紅/黑樹狀結構中,使其有序存放,並滿足紅/黑樹狀結構的性質。
4、帶有SortedMap的構造方法
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) { } }
首先將比較子指定為m的比較子,這取決於產生m時調用構造方法是否傳入了指定的構造器,而後調用buildFromSorted方法,將SortedMap中的元素插入到TreeMap中,由於SortedMap中的元素師有序的,實際上它是根據SortedMap建立的TreeMap,將SortedMap中對應的元素添加到TreeMap中。