1.TreehMap的內部結構
2.TreehMap構造函數
3.元素新增策略
4.元素刪除
5.元素修改和查找
6.特殊操做
7.擴容
8.總結html
首先確認一點,treemap是一個基於紅黑樹的map,這個集合的一個特色就是排序,是的若是不是排序,那麼hashmap能夠完美取代java
再開始前咱們要熟悉一個紅黑樹的概念:node
對於紅黑樹的定義:函數
1.節點是紅色或黑色。
2.根是黑色。
3.全部葉子都是黑色(葉子是NIL節點)。
4.每一個紅色節點必須有兩個黑色的子節點。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點。)
5.從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。ui
經過已排序的map進行新的treemap的構造生成,這裏用到了一個buildFromSorted函數,這個方法會遞歸數據this
參考buildFromSorted實現,這個實現的紅黑樹,說白了,就是把最後一層變成紅色,以上全做爲黑色spa
Put操做.net
這個操做沒啥,就是遍歷這顆樹,左邊小,右邊大,遍歷到合適的位置設置值,或者建立新的節點插入,並默認設置爲黑色
重點在於後面的變更以後,若是進行紅黑樹的修復
針對紅黑樹的變更,能夠參考以上總結的規則:https://www.cnblogs.com/cutter-point/p/10976416.html3d
針對於紅黑樹的操做主要就是左旋和右旋的操做指針
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { //若是根節點爲空,而後這個比較實際上是起一個類型檢查做用 compare(key, key); //建立root節點 root = new Entry<>(key, value, null); size = 1; modCount++; return null; } //若是根節點存在 int cmp; Entry<K,V> parent; //判斷是否設置了比較器 Comparator<? super K> cpr = comparator; 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(); //就提早key的比較器 @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); } //若是一直到t爲空還沒找到,那麼就建立新值 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; } //獲取對應節點的父節點 private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) { return (p == null ? null: p.parent); } //求當前節點的左右節點 private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) { return (p == null) ? null: p.left; } private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) { return (p == null) ? null: p.right; } //獲取當前節點的顏色,爲空即爲黑 private static <K,V> boolean colorOf(Entry<K,V> p) { return (p == null ? BLACK : p.color); } private static <K,V> void setColor(Entry<K,V> p, boolean c) { if (p != null) p.color = c; } 紅黑樹的修復操做,主要重點就是對從一個節點到葉子節點的黑色節點個數相同爲基準 //進行紅黑樹修復 private void fixAfterInsertion(Entry<K,V> x) { x.color = RED; //新增的節點默認是紅色,而後判斷是進行左旋,右旋,仍是其餘操做 //進行旋轉操做的前提是對應節點的父節點是紅色 while (x != null && x != root && x.parent.color == RED) { //判斷父節點 是不是祖父的左節點 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { //獲取祖父節點的右節點 Entry<K,V> y = rightOf(parentOf(parentOf(x))); //判斷另一個節點是紅是黑 if (colorOf(y) == RED) { //若是祖父的兄弟節點是紅色,那麼主要是吧兄弟節點改爲黑色便可,這樣祖父的兄弟節點至關於增長了一個黑色個數 //若是是紅,那麼就直接修改顏色便可 setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { //若是是黑色,說明兩邊的分支走到葉子節點的不是相同數目的黑色節點 //若是x是右節點,那麼就左旋,若是是左節點就右旋 if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } //最後根節點必定是黑 root.color = BLACK; } 左旋右旋操做就是把以對應的節點爲核心進行節點的上升和降低,而後要複合紅黑樹的規範 private void rotateLeft(Entry<K,V> p) { //左旋操做 if (p != null) { //若是節點不爲空,進行左旋的時候,獲取節點的右節點 Entry<K,V> r = p.right; p.right = r.left; if (r.left != null) r.left.parent = p; r.parent = p.parent; if (p.parent == null) root = r; else if (p.parent.left == p) p.parent.left = r; else p.parent.right = r; r.left = p; p.parent = r; } } /** From CLR */ private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
進行元素刪除的思想是:
若是我須要刪除這個節點,那麼首選判斷右子樹是否存在,若是存在那麼就找到右邊子樹的最小值,也就是最小的葉子節點,用一個最靠近的節點的數據替換須要刪除的節點的數據,這樣就保障二叉索引樹的特性,而後根據變更的節點顏色從新修復這顆紅黑樹的顏色
//節點刪除操做 public V remove(Object key) { //獲取到這個節點 Entry<K,V> p = getEntry(key); if (p == null) return null; //獲取舊值 V oldValue = p.value; //刪除節點,而後返回舊值 deleteEntry(p); return oldValue; } final Entry<K,V> getEntry(Object key) { //若是有設置比較器,那麼就優先使用比較器進行尋找 if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; //由於是紅黑樹,能夠判斷節點值的大小,而後判斷是左右節點的遍歷 while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } //進行節點的刪除 private void deleteEntry(Entry<K,V> p) { modCount++; size--; // 當前節點有兩個子節點,找到右子樹中最小元素做爲後繼節點;將後繼節點信息替換到當前節點 if (p.left != null && p.right != null) { //說白了就是找這個p元素相鄰大小的元素,優先找大的,其次找小的 //1.找右子樹的最小節點 由於上面有判斷p的左右節點必須存在,因此結果確定是右子樹的最小值 Entry<K,V> s = successor2(p); p.key = s.key; p.value = s.value; //吧引用指向s,吧p的值設置爲s的值 //這個時候須要刪除的那個節點的值變成了一個新的最靠近的值,這樣就不會破壞索引樹的條件,而後把那個用來替換的節點幹掉便可 p = s; //替換刪除的元素 } // 開始修復,優先取這個節點的左邊,不然取右邊做爲replacement節點對象 //除非是p.right和left有一個爲空,否則通常確定走的是p.right而且是個null對象 Entry<K,V> replacement = (p.left != null ? p.left : p.right); //若是要進行取代的節點爲空,那麼就不用操做 // 一、有兩個兒子。這種狀況比較複雜,但仍是比較簡單。上面提到過用子節點C替代代替待刪除節點D,而後刪除子節點C便可。 // 二、沒有兒子,即爲葉結點。直接把父結點的對應兒子指針設爲NULL,刪除兒子結點就OK了。 // 三、只有一個兒子。那麼把父結點的相應兒子指針指向兒子的獨生子,刪除兒子結點也OK了。 if (replacement != null) { //把須要替換的節點的父節點設置爲p的父節點 replacement.parent = p.parent; //判斷p的父節點是否Wie空,或者判斷p是不是做爲左節點,不然判斷是不是右節點 //說白了就是用replacement 取代P節點 if (p.parent == null) root = replacement; else if (p == p.parent.left) p.parent.left = replacement; else p.parent.right = replacement; //設置p的左右和父節點爲空,也就是吧p節點剝離開 p.left = p.right = p.parent = null; // 修復顏色分配,由於最後一個要刪除的節點是黑色,那麼刪除這個節點以後,這條線的黑色節點個數確定會減去1 if (p.color == BLACK) //那麼咱們就須要對這個變更過的節點進行調整 fixAfterDeletion(replacement); } else if (p.parent == null) { // return if we are the only node. root = null; } else { //通常狀況是這個,也就是說吧須要刪除的節點移動到末尾葉子節點,而後把key,value替換掉,最後刪除調最後一個葉子便可 //而後若是葉子的顏色正好是黑色的,那麼要從新修復顏色 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; } } } static <K,V> TestTreeMap.Entry<K,V> successor2(Entry<K,V> t) { //找到右邊節點的最小元素,也就是僅僅比T大一點的元素 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } private void fixAfterDeletion(Entry<K,V> x) { //判斷替換過的節點是不是黑色,刪除節點須要一直迭代,直到 x 不是根節點,且 x 的顏色是黑色 while (x != root && colorOf(x) == BLACK) { //若是這個節點是左節點 if (x == leftOf(parentOf(x))) { //獲取兄弟節點 Entry<K,V> sib = rightOf(parentOf(x)); if (colorOf(sib) == RED) { //兄弟節點爲紅,設置爲黑,父輩設置爲紅,而後左旋 setColor(sib, BLACK); setColor(parentOf(x), RED); //左旋 rotateLeft(parentOf(x)); sib = rightOf(parentOf(x)); } if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(rightOf(sib)) == BLACK) { setColor(leftOf(sib), BLACK); setColor(sib, RED); //右旋 rotateRight(sib); sib = rightOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } else { // symmetric Entry<K,V> sib = leftOf(parentOf(x)); if (colorOf(sib) == RED) { setColor(sib, BLACK); setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { setColor(sib, RED); x = parentOf(x); } else { if (colorOf(leftOf(sib)) == BLACK) { setColor(rightOf(sib), BLACK); setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } setColor(sib, colorOf(parentOf(x))); setColor(parentOf(x), BLACK); setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); }
修改能夠參考put操做,對key無影響
查找由於是二叉索引樹,因此查找方式和remove中的getEntry操做相似
6.1BuildFromSorted
這函數用來根據一級拍好順序的map構建treemap
再這個函數以前還有一個computeRedLevel函數,這個用來計算當前節點所在的層數
//計算紅色節點應該在紅黑樹哪一層,由於二叉樹,由於每層二叉樹要填滿的話必須是2的倍數 //每層數據疊加是1,1+2,1+2+4,1+2+4+8.。。 基本就是每層就是每層/2 //sz指樹中節點的個數,爲了確保是一個紅黑樹,那麼須要把前面幾層所有當作黑色,最後一層設置爲紅色便可 //由於sz是節點的個數,因此最後一個節點所在的層數便是紅色 public static int computeRedLevel(int sz) { int level = 0; //從0開始計算0,2,6,14 //能夠看出m=(m+1)*2 前一個和後一個的遞推關係 每一層計算 //那麼反過來就是m/2-1就是上一層的位置,最後一個m>=0的時候還要計算一次 for (int m = sz - 1; m >= 0; m = m / 2 - 1) level++; return level; } //經過迭代構造一個新的排序的map,遞歸將SortedMap中的元素逐個關聯 //str: 若是不爲空,則從流裏讀取key-value,defaultVal:見名知意,不爲空,則value都用這個值 private void buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal) throws java.io.IOException, ClassNotFoundException { this.size = size; //遞歸,添加到root節點上 root = buildFromSorted(0, 0, size-1, computeRedLevel(size), it, str, defaultVal); } /** * level: 當前樹的層數,注意:是從0層開始 * lo: 子樹第一個元素的索引 * hi: 子樹最後一個元素的索引 * redLevel: 上述紅節點所在層數 * 剩下的3個就不解釋了,跟上面的同樣 */ private final Entry<K,V> buildFromSorted(int level, int lo, int hi, int redLevel, Iterator<?> it, java.io.ObjectInputStream str, V defaultVal) throws java.io.IOException, ClassNotFoundException { /* * Strategy: The root is the middlemost element. To get to it, we * have to first recursively construct the entire left subtree, * so as to grab all of its elements. We can then proceed with right * subtree. * * The lo and hi arguments are the minimum and maximum * indices to pull out of the iterator or stream for current subtree. * They are not actually indexed, we just proceed sequentially, * ensuring that items are extracted in corresponding order. */ if (hi < lo) return null; //這至關於除以二,取中間位置,至關於除以2 int mid = (lo + hi) >>> 1; Entry<K,V> left = null; //子樹第一個元素的索引開始到中間的位置做爲左子樹,右邊剩下遞歸又右子樹 if (lo < mid) //遞歸左邊部分節點 left = buildFromSorted(level+1, lo, mid - 1, redLevel, it, str, defaultVal); K key; V value; //經過迭代器遍歷全部節點 if (it != null) { if (defaultVal==null) { Map.Entry<?,?> entry = (Map.Entry<?,?>)it.next(); key = (K)entry.getKey(); value = (V)entry.getValue(); } else { key = (K)it.next(); value = defaultVal; } } else { // use stream,經過流讀取對象 key = (K) str.readObject(); value = (defaultVal != null ? defaultVal : (V) str.readObject()); } //建立中間節點 Entry<K,V> middle = new Entry<>(key, value, null); // color nodes in non-full bottommost level red if (level == redLevel) middle.color = RED; if (left != null) { middle.left = left; left.parent = middle; } //遞歸右邊節點 if (mid < hi) { Entry<K,V> right = buildFromSorted(level+1, mid+1, hi, redLevel, it, str, defaultVal); middle.right = right; right.parent = middle; } return middle; }
這裏能夠總結一點:左旋和右旋的判斷主要依據是=》從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點。由於咱們每次新增的節點都是紅色,因此這個紅色的節點就會破壞原來的結構,會再紅色的null節點新增一個黑色null節點,爲了修復這種狀況,那麼就須要對父節點下的另一個節點進行修復
不存在擴容問題,二叉樹嘛,更相似鏈表的結構
總結就是不管是新增仍是刪除,再修復顏色的時候,維持從任一節點到其每一個葉子的全部簡單路徑都包含相同數目的黑色節點這個原則
因此通常會校驗這個節點的兄弟,以及父輩的兄弟節點,至於進行右旋仍是左旋,這個參考我以前的博客的內容以及規則
參考:
https://blog.csdn.net/cyywxy/article/details/81151104https://www.jianshu.com/p/e11fe1760a3d