JDK容器學習之TreeMap (一) : 底層數據結構

TreeMap

在平常的工做中,相比較與HashMap而言,TreeMap的使用會少不少,即便在某些場景,須要使用到排序的Map時,也更多的是選擇 LinkedHashMap,那麼這個TreeMap究竟是個怎樣的容器,又適用於什麼樣的應用場景呢?java

1. 數據結構分析

分析數據結構,最好的方式無疑是google+baidu+源碼了數據結構

1. 繼承體系

看到源碼第一眼,就會發現與HashMap不一樣的是 TreeMap 實現的是 NavigableMap, 而不是直接實現 Map學習

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

有必要仔細看下這個 NavigableMap,到底有些什麼特殊之處this

繼承體系: Map -> SortMap -> NavigbleMapgoogle

其中 SortMap 新增了下面幾個接口,目前也不知道具體有啥用,先翻譯下源碼註釋.net

// 既然叫作SortMap, 要排序的話,固然須要一個比較器了
Comparator<? super K> comparator();

SortedMap<K,V> subMap(K fromKey, K toKey);

// 源碼註釋: 返回比Map中key比參數toKey小的全部kv對
SortedMap<K,V> headMap(K toKey);

// 源碼註釋:返回比Map中key比參數fromKey大或相等的全部kv對
SortedMap<K,V> tailMap(K fromKey);

K firstKey();

K lastKey();

接着就是 NavigableMap 定義的接口翻譯

// 返回Map中比傳入參數key小的kv對中,key最大的一個kv對
Map.Entry<K,V> lowerEntry(K key);
K lowerKey(K key);

// 返回Map中比傳入參數key小或相等的kv對中,key最大的一個kv對
Map.Entry<K,V> floorEntry(K key);
K floorKey(K key);

// 返回Map中比傳入參數key大或相等的kv對中,key最小的一個kv對
Map.Entry<K,V> ceilingEntry(K key);
K ceilingKey(K key);

// 返回Map中比傳入參數key大的kv對中,key最小的一個kv對
Map.Entry<K,V> higherEntry(K key);
K higherKey(K key);


Map.Entry<K,V> firstEntry();
Map.Entry<K,V> lastEntry();
Map.Entry<K,V> pollFirstEntry();
NavigableMap<K,V> descendingMap();
NavigableSet<K> navigableKeySet();
NavigableSet<K> descendingKeySet();

基本上這兩個接口就是提供了一些基於排序的獲取kv對的方式code

2. 數據結構

看下內部的成員變量,發現可能涉及到數據結構的就只有下面的這個root了blog

private transient Entry<K,V> root;

結合 TreeMap 的命名來看,底層的結構多半就真的是Tree了,有樹的根節點,通常來說遍歷都是沒啥問題的排序

接下來看下 Entry的實現

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;

  /**
   * Make a new cell with given key, value, and parent, and with
   * {@code null} child links, and BLACK color.
   */
  Entry(K key, V value, Entry<K,V> parent) {
      this.key = key;
      this.value = value;
      this.parent = parent;
  }

  /**
   * Returns the key.
   *
   * @return the key
   */
  public K getKey() {
      return key;
  }

  /**
   * Returns the value associated with the key.
   *
   * @return the value associated with the key
   */
  public V getValue() {
      return value;
  }

  /**
   * Replaces the value currently associated with the key with the given
   * value.
   *
   * @return the value associated with the key before this method was
   *         called
   */
  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;
  }
}

從Entry的內部成員變量能夠看出,這是一個二叉樹,且極有可能就是一顆紅黑樹(由於有個black

2. 添加一個kv對

經過新增一個kv對的調用鏈,來分析下這棵樹,究竟是不是紅黑樹

將put方法撈出來, 而後補上註釋

public V put(K key, V value) {
  Entry<K,V> t = root;
  if (t == null) {
      // 奇怪的一行邏輯,感受並無什麼用
      compare(key, key); // type (and possibly null) check

      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) {
      // 下面這個循環能夠得出樹的左節點小於根小於右節點
      // 而後找到新增的節點,做爲葉子節點在樹中的位置
      // 注意這個相等時,直接更新了value值(這裏表示插入一條已存在的記錄)
      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 { 
      // 比較器不存在的邏輯,這時要求key繼承 Comparable 接口
      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);
  }
  
  // 建立一個Entry節點
  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;
}

從添加邏輯,能夠得出結論:

  1. 樹結構爲二叉排序樹(且不能出現相等的狀況)
  2. 重排的方法能夠保證該樹爲紅黑樹

因此新增一個kv對的邏輯就比較簡單了

遍歷樹,將kv對做爲葉子節點存在對應的位置

小結

紅黑樹相關能夠做爲獨立的一個知識點,這裏不詳細展開,基本上經過上面的分析,能夠得出下面幾個點

  1. TreeMap 底層結構爲紅黑樹
  2. 紅黑樹的Node排序是根據Key進行比較
  3. 每次新增刪除節點,均可能致使紅黑樹的重排
  4. 紅黑樹中不支持兩個or已上的Node節點對應紅黑值相等

相關博文

關注更多

掃一掃二維碼,關注 小灰灰blog

https://static.oschina.net/uploads/img/201709/22221611_Fdo5.jpg

相關文章
相關標籤/搜索