看這篇博客前,能夠先看下下列這幾篇博客java
TreeMap 的實現使用了紅黑樹數據結構,也就是一棵自平衡的排序二叉樹,這樣就能夠保證快速檢索指定節點。對於 TreeMap 而言,它採用一種被稱爲「紅黑樹」的排序二叉樹來保存 Map 中每一個 Entry —— 每一個 Entry 都被當成「紅黑樹」的一個節點對待。舉例:
當程序執行 map.put("ccc" , 89.0); 時,系統將直接把 "ccc"-89.0 這個 Entry 放入 Map 中,這個 Entry 就是該「紅黑樹」的根節點。接着程序執行 map.put("aaa" , 80.0); 時,程序會將 "aaa"-80.0 做爲新節點添加到已有的紅黑樹中。算法
之後每向 TreeMap 中放入一個 key-value 對,系統都須要將該 Entry 當成一個新節點,添加成已有紅黑樹中,經過這種方式就可保證 TreeMap 中全部 key 老是由小到大地排列。例如咱們輸出上面程序,將看到以下結果(全部 key 由小到大地排列):數組
{aaa=80.0, bbb=89.0, ccc=89.0, zzz=80.0}
TreeMap的添加節點(put()方法)
對於 TreeMap 而言,因爲它底層採用一棵「紅黑樹」來保存集合中的 Entry,這意味這 TreeMap 添加元素、取出元素的性能都比 HashMap 低(紅黑樹和Hash數據結構上的區別):當 TreeMap 添加元素時,須要經過循環找到新增 Entry 的插入位置,所以比較耗性能;當從 TreeMap 中取出元素時,須要經過循環才能找到合適的 Entry,也比較耗性能。但 TreeMap、TreeSet 比 HashMap、HashSet 的優點在於:TreeMap 中的全部 Entry 老是按 key 根據指定排序規則保持有序狀態,TreeSet 中全部元素老是根據指定排序規則保持有序狀態。
爲了很好的理解TreeMap你必須先理解紅黑樹,然而紅黑樹又是一種特殊的二叉查找樹,因此你必須先看兩篇博客
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 若是被刪除節點的左子樹、右子樹都不爲空 if (p.left != null && p.right != null) { // 用 p 節點的中序後繼節點代替 p 節點 Entry<K,V> s = successor (p); p.key = s.key; p.value = s.value; p = s; } // 若是 p 節點的左節點存在,replacement 表明左節點;不然表明右節點。 Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { replacement.parent = p.parent; // 若是 p 沒有父節點,則 replacemment 變成父節點 if (p.parent == null) root = replacement; // 若是 p 節點是其父節點的左子節點 else if (p == p.parent.left) p.parent.left = replacement; // 若是 p 節點是其父節點的右子節點 else p.parent.right = replacement; p.left = p.right = p.parent = null; // 修復紅黑樹 if (p.color == BLACK) fixAfterDeletion(replacement); // ① } // 若是 p 節點沒有父節點 else if (p.parent == null) { root = null; } else { if (p.color == BLACK) // 修復紅黑樹 fixAfterDeletion(p); // ② if (p.parent != null) { // 若是 p 是其父節點的左子節點 if (p == p.parent.left) p.parent.left = null; // 若是 p 是其父節點的右子節點 else if (p == p.parent.right) p.parent.right = null; p.parent = null; } } }
檢索節點數據結構
當 TreeMap 根據 key 來取出 value 時,TreeMap 對應的方法以下:多線程
public V get(Object key) { // 根據指定 key 取出對應的 Entry Entry>K,V< p = getEntry(key); // 返回該 Entry 所包含的 value return (p==null ? null : p.value); }
從上面程序的粗體字代碼能夠看出,get(Object key) 方法實質是因爲 getEntry() 方法實現的,這個 getEntry() 方法的代碼以下:ide
final Entry<K,V> getEntry(Object key) { // 若是 comparator 不爲 null,代表程序採用定製排序 if (comparator != null) // 調用 getEntryUsingComparator 方法來取出對應的 key return getEntryUsingComparator(key); // 若是 key 形參的值爲 null,拋出 NullPointerException 異常 if (key == null) throw new NullPointerException(); // 將 key 強制類型轉換爲 Comparable 實例 Comparable<? super K> k = (Comparable<? super K>) key; // 從樹的根節點開始 Entry<K,V> p = root; while (p != null) { // 拿 key 與當前節點的 key 進行比較 int cmp = k.compareTo(p.key); // 若是 key 小於當前節點的 key,向「左子樹」搜索 if (cmp < 0) p = p.left; // 若是 key 大於當前節點的 key,向「右子樹」搜索 else if (cmp > 0) p = p.right; // 不大於、不小於,就是找到了目標 Entry else return p; } return null; }
上面的 getEntry(Object obj) 方法也是充分利用排序二叉樹的特徵來搜索目標 Entry,程序依然從二叉樹的根節點開始,若是被搜索節點大於當前節點,程序向「右子樹」搜索;若是被搜索節點小於當前節點,程序向「左子樹」搜索;若是相等,那就是找到了指定節點。工具
當 TreeMap 裏的 comparator != null 即代表該 TreeMap 採用了定製排序,在採用定製排序的方式下,TreeMap 採用 getEntryUsingComparator(key) 方法來根據 key 獲取 Entry。下面是該方法的代碼:性能
final Entry<K,V> getEntryUsingComparator(Object key) { K k = (K) key; // 獲取該 TreeMap 的 comparator Comparator<? super K> cpr = comparator; if (cpr != null) { // 從根節點開始 Entry<K,V> p = root; while (p != null) { // 拿 key 與當前節點的 key 進行比較 int cmp = cpr.compare(k, p.key); // 若是 key 小於當前節點的 key,向「左子樹」搜索 if (cmp < 0) p = p.left; // 若是 key 大於當前節點的 key,向「右子樹」搜索 else if (cmp > 0) p = p.right; // 不大於、不小於,就是找到了目標 Entry else return p; } } return null; }
其實 getEntry、getEntryUsingComparator 兩個方法的實現思路徹底相似,只是前者對天然排序的 TreeMap 獲取有效,後者對定製排序的 TreeMap 有效。ui
經過上面源代碼的分析不難看出,TreeMap 這個工具類的實現其實很簡單。或者說:從內部結構來看,TreeMap 本質上就是一棵「紅黑樹」,而 TreeMap 的每一個 Entry 就是該紅黑樹的一個節點。spa
其實這個問題就是在問紅黑樹相對於排序二叉樹的優勢。咱們都知道排序二叉樹雖然能夠快速檢索,但在最壞的狀況下:若是插入的節點集自己就是有序的,要麼是由小到大排列,要麼是由大到小排列,那麼最後獲得的排序二叉樹將變成鏈表:全部節點只有左節點(若是插入節點集自己是大到小排列);或全部節點只有右節點(若是插入節點集自己是小到大排列)。在這種狀況下,排序二叉樹就變成了普通鏈表,其檢索效率就會不好。
爲了改變排序二叉樹存在的不足,Rudolf Bayer 與 1972 年發明了另外一種改進後的排序二叉樹:紅黑樹,他將這種排序二叉樹稱爲「對稱二叉 B 樹」,而紅黑樹這個名字則由 Leo J. Guibas 和 Robert Sedgewick 於 1978 年首次提出。
紅黑樹是一個更高效的檢索二叉樹,所以經常用來實現關聯數組。典型地,JDK 提供的集合類 TreeMap 自己就是一個紅黑樹的實現。
紅黑樹在原有的排序二叉樹增長了以下幾個要求:
Java 中實現的紅黑樹可能有如圖 6 所示結構:
備註:本文中全部關於紅黑樹中的示意圖採用白色表明紅色。黑色節點仍是採用了黑色表示。
根據性質 5:紅黑樹從根節點到每一個葉子節點的路徑都包含相同數量的黑色節點,所以從根節點到葉子節點的路徑中包含的黑色節點數被稱爲樹的「黑色高度(black-height)」。
性質 4 則保證了從根節點到葉子節點的最長路徑的長度不會超過任何其餘路徑的兩倍。假若有一棵黑色高度爲 3 的紅黑樹:從根節點到葉節點的最短路徑長度是 2,該路徑上全是黑色節點(黑節點 - 黑節點 - 黑節點)。最長路徑也只可能爲 4,在每一個黑色節點之間插入一個紅色節點(黑節點 - 紅節點 - 黑節點 - 紅節點 - 黑節點),性質 4 保證毫不可能插入更多的紅色節點。因而可知,紅黑樹中最長路徑就是一條紅黑交替的路徑。
由此咱們能夠得出結論:對於給定的黑色高度爲 N 的紅黑樹,從根到葉子節點的最短路徑長度爲 N-1,最長路徑長度爲 2 * (N-1)。
提示:排序二叉樹的深度直接影響了檢索的性能,正如前面指出,當插入節點自己就是由小到大排列時,排序二叉樹將變成一個鏈表,這種排序二叉樹的檢索性能最低:N 個節點的二叉樹深度就是 N-1。
紅黑樹經過上面這種限制來保證它大體是平衡的——由於紅黑樹的高度不會無限增高,這樣保證紅黑樹在最壞狀況下都是高效的,不會出現普通排序二叉樹的狀況。
因爲紅黑樹只是一個特殊的排序二叉樹,所以對紅黑樹上的只讀操做與普通排序二叉樹上的只讀操做徹底相同,只是紅黑樹保持了大體平衡,所以檢索性能比排序二叉樹要好不少。
但在紅黑樹上進行插入操做和刪除操做會致使樹再也不符合紅黑樹的特徵,所以插入操做和刪除操做都須要進行必定的維護,以保證插入節點、刪除節點後的樹依然是紅黑樹。
」TreeMap、TreeSet 對比 HashMap、HashSet的優缺點?「
缺點:
對於 TreeMap 而言,因爲它底層採用一棵「紅黑樹」來保存集合中的 Entry,這意味這 TreeMap 添加元素、取出元素的性能都比 HashMap (O(1))低:
TreeMap 中的全部 Entry 老是按 key 根據指定排序規則保持有序狀態,TreeSet 中全部元素老是根據指定排序規則保持有序狀態。