2019獨角獸企業重金招聘Python工程師標準>>>
前言
? ? 本文不打算延續前幾篇的風格(對所有的源碼加入注釋),因為要理解透TreeMap的所有源碼,對博主來說,確實需要耗費大量的時間和經歷,目前看來不大可能有這么多時間的投入,故這里意在通過于閱讀源碼對TreeMap有個宏觀上的把握,并就其中一些方法的實現做比較深入的分析。
?
紅黑樹簡介
?
? ? TreeMap是基于紅黑樹實現的,這里只對紅黑樹做個簡單的介紹,紅黑樹是一種特殊的二叉排序樹,關于二叉排序樹,參見:http://blog.csdn.net/ns_code/article/details/19823463,紅黑樹通過一些限制,使其不會出現二叉樹排序樹中極端的一邊倒的情況,相對二叉排序樹而言,這自然提高了查詢的效率。
? ? 二叉排序樹的基本性質如下:
? ??1、每個節點都只能是紅色或者黑色
?
? ? 2、根節點是黑色
? ? 3、每個葉節點(NIL節點,空節點)是黑色的。
? ??4、如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
? ? 5、從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
? ? 正是這些性質的限制,使得紅黑樹中任一節點到其子孫葉子節點的最長路徑不會長于最短路徑的2倍,因此它是一種接近平衡的二叉樹。
? ? 說到紅黑樹,自然不免要和AVL樹對比一番。相比較而言,AVL樹是嚴格的平衡二叉樹,而紅黑樹不算嚴格意義上的平衡二叉樹,只是接近平衡,不會讓樹的高度如BST極端情況那樣等于節點的個數。其實能用到紅黑樹的地方,也都可以用AVL樹來實現,但紅黑樹的應用卻非常廣泛,而AVL樹則很少被使用。在執行插入、刪除操作時,AVL樹需要調整的次數一般要比紅黑樹多(紅黑樹的旋轉調整最多只需三次),效率相對較低,且紅黑樹的統計性能較AVL樹要好,當然AVL樹在查詢效率上可能更勝一籌,但實際上也高不了多少。
? ? 紅黑樹的插入刪除操作很簡單,就是單純的二叉排序樹的插入刪除操作。紅黑樹被認為比較變態的地方自然在于插入刪除后對紅黑樹的調整操作(旋轉和著色),主要是情況分的很多,限于篇幅及博主的熟悉程度優先,這里不打算詳細介紹插入刪除后調整紅黑樹的各種情況及其實現,我們有個宏觀上的了解即可,如須詳細了解,參見算法導論或一些相關的資料。
?
TreeMap源碼剖析
? ? 存儲結構
?
? ? TreeMap的排序是基于對key的排序實現的,它的每一個Entry代表紅黑樹的一個節點,Entry的數據結構如下:
?
[java]?view plain?copy
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、無參構造方法
?
[java]?view plain?copy
public?TreeMap()?{????????comparator?=?null;????}????? ? 采用無參構造方法,不指定比較器,這時候,排序的實現要依賴key.compareTo()方法,因此key必須實現Comparable接口,并覆寫其中的compareTo方法。
?
? ? 2、帶有比較器的構造方法
?
[java]?view plain?copy
public?TreeMap(Comparator<??super?K>?comparator)?{????????this.comparator?=?comparator;????}????? ? 采用帶比較器的構造方法,這時候,排序依賴該比較器,key可以不用實現Comparable接口。
?
? ? 3、帶Map的構造方法
?
[java]?view plain?copy
public?TreeMap(Map<??extends?K,???extends?V>?m)?{????????comparator?=?null;????????putAll(m);????}????? ? 該構造方法同樣不指定比較器,調用putAll方法將Map中的所有元素加入到TreeMap中。putAll的源碼如下:
?
?
[java]?view plain?copy
//?將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)方法,該方法源碼如下:
?
?
[java]?view plain?copy
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的構造方法
?
[java]?view plain?copy
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中。
?
?
? ? 插入刪除
?
?
? ? 插入操作即對應TreeMap的put方法,put操作實際上只需按照二叉排序樹的插入步驟來操作即可,插入到指定位置后,再做調整,使其保持紅黑樹的特性。put源碼的實現:
?
[java]?view plain?copy
public?V?put(K?key,?V?value)?{????????Entry<K,V>?t?=?root;????????//?若紅黑樹為空,則插入根節點????????if?(t?==?null)?{????????//?TBD:????????//?5045147:?(coll)?Adding?null?to?an?empty?TreeSet?should????????//?throw?NullPointerException????????//????????//?compare(key,?key);?//?type?check????????????root?=?new?Entry<K,V>(key,?value,?null);????????????size?=?1;????????????modCount++;????????????return?null;????????}????????int?cmp;????????Entry<K,V>?parent;????????//?split?comparator?and?comparable?paths????????Comparator<??super?K>?cpr?=?comparator;????????//?找出(key,?value)在二叉排序樹中的插入位置。????????//?紅黑樹是以key來進行排序的,所以這里以key來進行查找。????????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);????????????}?while?(t?!=?null);????????}????????else?{????????????if?(key?==?null)????????????????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);????????}????????//?為(key-value)新建節點????????Entry<K,V>?e?=?new?Entry<K,V>(key,?value,?parent);????????if?(cmp?<?0)????????????parent.left?=?e;????????else???????????parent.right?=?e;????????//?插入新的節點后,調用fixAfterInsertion調整紅黑樹。????????fixAfterInsertion(e);????????size++;????????modCount++;????????return?null;????}????? ? 這里的fixAfterInsertion便是節點插入后對樹進行調整的方法,這里不做介紹。
? ? 刪除操作及對應TreeMap的deleteEntry方法,deleteEntry方法同樣也只需按照二叉排序樹的操作步驟實現即可,刪除指定節點后,再對樹進行調整即可。deleteEntry方法的實現源碼如下:
?
?
[java]?view plain?copy
//?刪除“紅黑樹的節點p”????private?void?deleteEntry(Entry<K,V>?p)?{????????modCount++;????????size--;??????????if?(p.left?!=?null?&&?p.right?!=?null)?{????????????Entry<K,V>?s?=?successor?(p);????????????p.key?=?s.key;????????????p.value?=?s.value;????????????p?=?s;????????}?????????Entry<K,V>?replacement?=?(p.left?!=?null???p.left?:?p.right);??????????if?(replacement?!=?null)?{????????????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;??????????????p.left?=?p.right?=?p.parent?=?null;??????????????if?(p.color?==?BLACK)????????????????fixAfterDeletion(replacement);????????}?else?if?(p.parent?==?null)?{???????????root?=?null;????????}?else?{??????????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;????????????}????????}????}????? ? 后面的fixAfterDeletion方法便是節點刪除后對樹進行調整的方法,這里不做介紹。
?
? ? 其他很多方法這里不再一一介紹。
幾點總結
? ? 本文對TreeMap的分析較前幾篇文章有些淺嘗輒止,TreeMap用的沒有HashMap那么多,我們有個宏觀上的把我和比較即可。
? ? 1、TreeMap是根據key進行排序的,它的排序和定位需要依賴比較器或覆寫Comparable接口,也因此不需要key覆寫hashCode方法和equals方法,就可以排除掉重復的key,而HashMap的key則需要通過覆寫hashCode方法和equals方法來確保沒有重復的key。
? ? 2、TreeMap的查詢、插入、刪除效率均沒有HashMap高,一般只有要對key排序時才使用TreeMap。
? ? 3、TreeMap的key不能為null,而HashMap的key可以為null。
?
? ? 注:對TreeSet和HashSet的源碼不再進行剖析,二者分別是基于TreeMap和HashMap實現的,只是對應的節點中只有key,而沒有value,因此對TreeMap和HashMap比較了解的話,對TreeSet和HashSet的理解就會非常容易。
轉載于:https://my.oschina.net/chinaxy/blog/1825460
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的【Java集合源码剖析】TreeMap源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。