Java集合源碼剖析:TreeMap源碼剖析

▷▷▷前言

本文不打算延續前幾篇的風格(對全部的源碼加入註釋),由於要理解透TreeMap的全部源碼,對博主來講,確實須要耗費大量的時間和經歷,目前看來不大可能有這麼多時間的投入,故這裏意在經過於閱讀源碼對TreeMap有個宏觀上的把握,並就其中一些方法的實現作比較深刻的分析。java

▷▷▷紅黑樹簡介

TreeMap是基於紅黑樹實現的,這裏只對紅黑樹作個簡單的介紹,紅黑樹是一種特殊的二叉排序樹,關於二叉排序樹,參見:blog.csdn.net/ns_code/art…算法

二叉排序樹的基本性質以下: 一、每一個節點都只能是紅色或者黑色 二、根節點是黑色 三、每一個葉節點(NIL節點,空節點)是黑色的。 四、若是一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。 五、從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。bash

正是這些性質的限制,使得紅黑樹中任一節點到其子孫葉子節點的最長路徑不會長於最短路徑的2倍,所以它是一種接近平衡的二叉樹。 說到紅黑樹,天然難免要和AVL樹對比一番。相比較而言,AVL樹是嚴格的平衡二叉樹,而紅黑樹不算嚴格意義上的平衡二叉樹,只是接近平衡,不會讓樹的高度如BST極端狀況那樣等於節點的個數。數據結構

其實能用到紅黑樹的地方,也均可以用AVL樹來實現,但紅黑樹的應用卻很是普遍,而AVL樹則不多被使用。在執行插入、刪除操做時,AVL樹須要調整的次數通常要比紅黑樹多(紅黑樹的旋轉調整最多隻需三次),效率相對較低,且紅黑樹的統計性能較AVL樹要好,固然AVL樹在查詢效率上可能更勝一籌,但實際上也高不了多少。 紅黑樹的插入刪除操做很簡單,就是單純的二叉排序樹的插入刪除操做。紅黑樹被認爲比較變態的地方天然在於插入刪除後對紅黑樹的調整操做(旋轉和着色),主要是狀況分的不少,限於篇幅及博主的熟悉程度優先,這裏不打算詳細介紹插入刪除後調整紅黑樹的各類狀況及其實現,咱們有個宏觀上的瞭解便可,如須詳細瞭解,參見算法導論或一些相關的資料。app

▷▷▷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個構造方法。 一、無參構造方法性能

public TreeMap() {    

                   comparator = null;    

             }  

複製代碼

採用無參構造方法,不指定比較器,這時候,排序的實現要依賴key.compareTo()方法,所以key必須實現Comparable接口,並覆寫其中的compareTo方法。ui

二、帶有比較器的構造方法this

public TreeMap(Comparator<? super K> comparator) {    

            this.comparator = comparator;    

        }  
複製代碼

採用帶比較器的構造方法,這時候,排序依賴該比較器,key能夠不用實現Comparable接口。spa

三、帶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中的元素是無序存放的,所以要一個個插入到紅黑樹中,使其有序存放,並知足紅黑樹的性質。

四、帶有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中。

插入刪除 插入操做即對應TreeMap的put方法,put操做實際上只需按照二叉排序樹的插入步驟來操做便可,插入到指定位置後,再作調整,使其保持紅黑樹的特性。put源碼的實現:

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方法的實現源碼以下:

// 刪除「紅黑樹的節點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那麼多,咱們有個宏觀上的把我和比較便可。 一、TreeMap是根據key進行排序的。它的排序和定位須要依賴比較器或覆寫Comparable接口,也所以不須要key覆寫hashCode方法和equals方法,就能夠排除掉重複的key,而HashMap的key則須要經過覆寫hashCode方法和equals方法來確保沒有重複的key。 二、TreeMap的查詢、插入、刪除效率均沒有HashMap高,通常只有要對key排序時才使用TreeMap。 三、TreeMap的key不能爲null,而HashMap的key能夠爲null。

▷▷▷注: 對TreeSet和HashSet的源碼再也不進行剖析,兩者分別是基於TreeMap和HashMap實現的,只是對應的節點中只有key,而沒有value,所以對TreeMap和HashMap比較瞭解的話,對TreeSet和HashSet的理解就會很是容易。

相關文章
相關標籤/搜索