標籤:linked oid oss private 中序 些許 區別 其它 儲存
目錄
概述
通常情況下儲存索引值對僅需要HashMap即可滿足需求, 但是HashMap有些許缺點, 比如: 1. 不能保證元素的順序, 因此產生了LinkedHashMap; 2. 不能對元素進行排序, 因此產生了本文中的TreeMap.
TreeMap實現了SortedMap介面, SortedMap介面中定義了排序機制, 以及或者頭元素或者尾元素等方法.
本文不會對每個方法進行說明, 只是撿重要的方法說明, 其它方法相信你看完本文就很容易懂的.
儲存結構
TreeMap雖然儲存了索引值對, 看錶面與HashMap沒什麼區別, 但是內部儲存結構卻截然相反.
| 類 |
儲存結構 |
| HashMap |
數組+鏈表/紅/黑樹狀結構 |
| TreeMap |
紅/黑樹狀結構 |
操作添加
在看源碼之前, 我們可以想一下, 對於排序二叉樹的插入操作是如何進行的?
從根開始比較, 如果新元素比樹中節點元素小, 就去與節點的左孩子比較大小, 如果新元素比樹中節點元素大, 就去與節點的右孩子比較大小, 那麼如果相同呢? 與HashMap中的處理一樣, 替換其value值.
源碼如下:
public V put(K key, V value) { // 擷取根節點 Entry<K,V> t = root; // 根節點為null --空樹 if (t == null) { // 檢查key是否為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; // 得到比較子 // 這雷根據比較子是否為null進行了區分 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); // 相等則替換其value值 } while (t != null); } else { if (key == null) throw new NullPointerException(); // key如果沒有實現Comparable介面, 那麼這裡就會報錯 @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<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; // 進行紅/黑樹狀結構的調整 fixAfterInsertion(e); size++; modCount++; return null;}
上面代碼只是元素的插入代碼, 插入新元素之後, 可能會破壞紅/黑樹狀結構的性質, 紅/黑樹狀結構的調整操作參考另一片文章: 大戰紅/黑樹狀結構, 也就是上面代碼中的fixAfterInsertion(e);方法.
刪除
刪除操作需要注意一下, 如果刪除的節點有兩個孩子節點, 則不能直接刪除節點, 而是需要找到其前驅或者後繼節點來替換該節點, 然後把前驅或者後繼節點刪除掉即可.
private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 有兩個孩子節點, 這裡是去尋找後繼節點(也就是中根遍曆時該節點的後一個節點) // 最後將待刪除的節點p指向後繼節點s // If strictly internal, copy successor‘s element to p and then make p // point to successor. if (p.left != null && p.right != null) { Entry<K,V> s = successor(p); p.key = s.key; p.value = s.value; p = s; } // p has 2 children // 判斷待刪除節點是否有孩子節點 // Start fixup at replacement node, if it exists. Entry<K,V> replacement = (p.left != null ? p.left : p.right); if (replacement != null) { // 有孩子節點 // 連結孩子節點到父節點上 // Link replacement to parent 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; // Null out links so they are OK to use by fixAfterDeletion. p.left = p.right = p.parent = null; // Fix replacement if (p.color == BLACK) // 如果刪除的是黑節點, 則需要進行紅/黑樹狀結構的調整 fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. root = null; } else { // No children. Use self as phantom replacement and unlink. if (p.color == BLACK) // 如果刪除的是黑節點, 則需要進行紅/黑樹狀結構的調整 fixAfterDeletion(p); // 刪除的是紅色節點, 進行null操作的賦值 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(p);方法.
遍曆
TreeMap的遍曆操作實際上就是二叉樹的中序遍曆操作.
遍曆操作時, 最重要的內部類就是EntryIterator, EntryIterator繼承PrivateEntryIterator, 重要的實現在PrivateEntryIterator中, 首先看到PrivateEntryIterator的構造方法需要傳入first元素, 也就是頭元素, 如何擷取頭元素呢? TreeMap使用了getFirstEntry()方法, 之後擷取下一個節點的時候使用尋找後繼的方法進行擷取即可.
懂了TreeMap中Entry的遍曆, 那麼key或者value的遍曆自然就知曉了.
擴容
TreeMap不涉及擴容操作, 因為它使用樹結構進行儲存.
總結
TreeMap除去紅/黑樹狀結構的調整之後, 就是非常簡單的二叉排序樹代碼, 學習時我們可以對其進行分離學習.
TreeMap源碼分析