TreeMap原始碼剖析

2022-09-16 09:18:14 字數 4452 閱讀 5683

原文  

主題 原始碼分析 

紅黑樹注:以下原始碼基於jdk1.7.0_11

之前介紹了一系列map集合中的具體實現類,包括hashmap,hashtable,linkedhashmap。這三個類都是基於雜湊表實現的,今天我們介紹另一種map集合,treemap。treemap是基於紅黑樹實現的。

介紹treemap之前,回顧下紅黑樹的性質

首先,我們要明確,紅黑樹是一種二叉排序樹,而且是平衡二叉樹。因而紅黑樹具有排序樹的所有特點,任意結點的左子樹(如果有的話)的值比該結點小,右子樹(如果有的話)的值比該結點大。二叉排序樹各項操作的平均時間複雜度為o(logn),但是最壞情況下,二叉排序樹會退化成單鏈表,此時時間複雜度為o(n),紅黑樹在二叉排序樹的基礎上,對其增加了一系列約束,使得其盡可能地平衡, 紅黑樹的查詢和更新的時間複雜度為o(logn)。

紅黑樹的五條性質:

1.每個結點要麼是紅色,要麼是黑色;

2.根結點為黑色;

3.葉結點為黑色(空結點);

4.若乙個結點為紅色,則其子結點為黑色;

5.每個葉結點到根結點的路徑中黑色結點的數目一致(黑高度相同)。

紅黑樹的查詢操作與二叉排序樹相同,重點是其插入和刪除操作。紅黑樹的插入和刪除操作在二叉排序樹的基礎上增加了修復操作,因為插入和刪除可能會導致樹不再滿足紅黑樹性質,這時候會通過著色、旋轉操作對其進行修復。

下面來看treemap的實現。

類宣告:

public class treemap

extends abstractmap

implements n**igablemap, cloneable, j**a.io.serializable

treemap同樣繼承abstractmap,但是它實現了n**igablemap介面,而n**igablemap介面繼承自sortedmap介面。

treemap有四個成員變數,其中root是紅黑樹的根結點, 由於紅黑樹的查詢和更新操作需要比較,故而有個比較器comparator,預設情況下,comparator為空,這就要求我們的鍵必須實現comparable介面,以定義比較規則。

private final comparator super k> comparator;//比較器

private transient entryroot = null;//樹根

/*** the number of entries in the tree

*/private transient int size = 0;//大小

/*** the number of structural modifications to the tree.

*/private transient int modcount = 0;//修改次數

構造器:

public treemap() 

public treemap(comparator super k> comparator)

public treemap(map extends k, ? extends v> m)

public treemap(sortedmapm)

}

在檢視treemap的查詢和更新操作之前,我們先看下entry的實現,其實我們都可以猜到,entry既然是treemap儲存的結點,那麼其必然包括如下幾個域:資料(鍵、值)、父結點、左孩子、右孩子、顏色。

事實正是如此:

static final class entry implements map.entry  child links, and black color.

*/entry(k key, v value, entryparent)

public k getkey()

public v getvalue()

public v setvalue(v value)

public boolean equals(object o)

public int hashcode()

public string tostring()

}

下面來看put方法:

public v put(k key, v value) 

int cmp;

entryparent;//父結點

// split comparator and comparable paths

comparator super k> cpr = comparator;

if (cpr != null) while (t != null);

} else while (t != null);

} //找到插入點之後,建立新結點,插入之。

entrye = new entry<>(key, value, parent);

if (cmp < 0)//判斷是掛到左邊還是右邊

parent.left = e;

else

parent.right = e;

fixafterinsertion(e);//進行著色和旋轉等操作修復紅黑樹

size++;

modcount++;

return null;

}

明確以下幾點:

1.treemap的查詢和更新操作都涉及到比較操作,故而treemap的鍵必須實現comparable介面或者構造時得傳入比較器(既實現了comparable介面又傳入了比較器情況下,比較器優先);

2.put操作不允許null鍵,但是值(value)允許為null;

3.鍵重複的情況下,新值會覆蓋掉舊值。

再看get方法:

public v get(object key) 

呼叫getentry方法查詢指定鍵值:

final entrygetentry(object key) 

return null;//沒找到

} final entrygetentryusingcomparator(object key)

} return null;

}

對比hashmap近乎o(1)的查詢複雜度,treemap顯得略有不足。

再看remove刪除操作:

public v remove(object key) 

雖然看上去寥寥幾行**,其實邏輯十分複雜,具體體現在刪除結點後的恢復操作。

private void deleteentry(entryp)  // p has 2 children

//下面操作將釋放s結點,並修復紅黑樹

// start fixup at replacement node, if it exists.

entryreplacement = (p.left != null ? p.left : p.right);

if (replacement != null) else if (p.parent == null) else

} }

successtor函式用於找乙個結點的中序後繼(參見之前寫的一篇如何得到乙個結點的中序後繼,演算法一致):

迭代器遍歷操作正是基於successtor操作完成的。所以遍歷treemap得到的鍵值對是有序的。

static treemap.entrysuccessor(entryt)  else 

return p;

} }

對稱地,還有個查詢直接前驅的函式:

static entrypredecessor(entryt)  else 

return p;

} }

注:文章故意忽略了更新操作中涉及到的紅黑樹修復動作(著色,旋轉),此部分內容較為複雜,作者目前也沒有完全吃透。

總結:1.treemap的實現基於紅黑樹;

2.treemap不允許插入null鍵,但允許null值;

3.treemap執行緒不安全;

4.插入結點時,若鍵重複,則新值會覆蓋舊值;

5.treemap要求key必須實現comparable介面,或者初始化時傳入comparator比較器;

6.遍歷treemap得到的結果集是有序的(中序遍歷);

7.treemap的各項操作的平均時間複雜度為o(logn).

TreeMap 原始碼分析

treemap底層是使用紅黑樹實現的儲存鍵值對的map容器,可以通過比較器進行排序。紅黑樹本質上是一棵弱平衡二叉樹,它的節點有紅色和黑色兩種顏色。主要特性有五點。1 每個節點或者是黑色,或者是紅色。2 根節點是黑色。3 每個葉子節點 nil 是黑色。注意 這裡葉子節點,是指為空 nil或null 的...

TreeMap原始碼分析

public v put k key,v value int cmp entryparent split comparator and comparable paths comparator cpr comparator if cpr null while t null else while t n...

TreeMap的原始碼

目錄 元素結構treemapentry 存放元素的邏輯 獲取元素 static final class treemapentryimplements map.entrypublic v put k key,v value int cmp treemapentryparent comparator c...