java 集合之TreeSet詳解
首先說明一下,之前看了一下文章提出TreeSet在新增第一個元素的時候是不比較大小的,這種說發是錯誤的,在第一次新增的時候比較的是第一個物件本省返回的引數是0,下面我們用程式驗證一下:
首先由一個Student的內部類:
裡面有兩個引數,年齡和名稱我們後期自定義排序也是用得到的。然後我們把這個物件新增到TreeSet中,構建一個無引數的TreeSet。
這樣新增我們是能看到程式報錯:
然後我們寫一個自定義的比較器Comparator來驗證一下傳入的第一個引數具體比較的物件:
在比較器中加一個斷點,驗證一下在第一次新增的時候比較的是什麼:
通過斷點我們可以看到
o1和o2是一樣的因此第一個元素在錄入到TreeSet中的時候比較的是他本身並且輸出num=0,控制檯輸出:
下面來詳細介紹一下TreeSet,首先定義我們從jdk1.8中看一下TreeSet是一個什麼東西:
A NavigableSet實現基於TreeMap 。 的元件使用其有序natural ordering ,或由Comparator集合建立時提供,這取決於所使用的構造方法。
此實現提供了基本的操作(保證的log(n)時間成本add , remove和contains )。
TreeSet是一個不同步的非執行緒安全的二叉樹。
Set<String> s2 = new TreeSet<>();
原始碼中是這樣是實現的:構建一個按自然排序的空樹集。必須實現Comparable介面。建立的是一個new TreeMap。
跟進到TreeMap中,建立一個新的空的tree map ,用作自然排序。所有的key的插入這個map必須實現Comparable介面。
用於維護元素的比較器。如果為null,則keys用的是自然排序。
上面簡單介紹了一下建立一個TreeSet,等於建立一個空的自然排序的TreeMap容器。
容器提供了兩種排序的方法,一種自然排序當comparator為空的時候,構建無參建構函式的時候預設的一種排序方式,下面看一下在往容器中新增值的時候是如何保證容器的順序的,看一下原始碼中add方法。
TreeSet的新增方法實現了map的put方法,其中key為我們新增的變數值,value為定值Object物件。
後面跟進看一下具體的put方法,裡面是如何實現對引數key的自然排序的。
我們來分析一下具體的實現通過新增幾個隨機數字分析下面先來貼上一下原始碼根據原始碼和具體值來分析:
上面為具體實現的原始碼資訊,我們來新增幾個值分析一下:1,7,8,9,3,2
(1)新增這個1的時候由於是第一次新增Entry<K,V> t = root; 此時t=null;
if (t == null) {
compare(key, key); // type (and possibly null) check《1》
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
進入這個方法,由此《1》處可以看出第一次在TreeSet中新增值的時候,元素對比的是key自身。那麼兩個key目前的值為1,跟進compare(key, key);這個方法我們看一下具體比較的演算法。
此時由於我們建構函式用的是無引數的沒有指定比較器所以comparator比較器為空
((Comparable<? super K>)k1).compareTo((K)k2)走的是這個 return 0;
然後直接新新增一條資料到Entry<k,v>中,並記錄size = 1,且記錄對樹進行結構修改的次數++。
那麼我們來思考一個問題?此時的root等於什麼?
是的,此時root中儲存的是我們剛剛錄入的那個元素,k=1,value=new Object();的一個定值。
此時的樹結構為:
(2)當我們新增7這個值的時候,put這個函式是怎麼執行的呢?下面我們分析一下:
新增7的時候此時Entry<k,v> = root; root = 1;
此時t!=null;定義int cmp; 和 上層父類Entry<K,V> parent;由於我們構造的是無引數的建構函式,所以Comparator<? super K> cpr = comparator; comparator為null,走else這個程式碼塊:
裡面有個判斷key為空的判斷,如果key為空丟擲異常,所以新增的key元素不能為空。
Comparable<? super K> k = (Comparable<? super K>) key;此時k=7,且t=1,t.key = 1;
cmp = k.compareTo(t.key); 這個比較的是 7 > 1,大於0,返回的cmp為1,也就是在根目錄1的Reght右邊:
此時樹結構為:
+
(3)那麼我們在新增一個8,看一下此時put函式是如何執行:
此時我們看一下root的值
key=1,根目錄為1 在1的右邊有一個值7
此時我們傳入的8為Comparable<? super K> k = (Comparable<? super K>) key;此時k=8,且t.key = 1;
cmp = k.compareTo(t.key) = 正數在 根目錄1的右邊。此時樹結構為:
此時結構的右邊有7,那麼還要跟7做一個比對cmp = k.compareTo(t.key) ,此時k=8 t.key = 7,cmp也是一個正數,在7的右邊,此時最後的樹結構為:
也就是後面這種
我們具體看一下程式中root的值,如下:
此時key = 7 , left = 1 ,right = 8。此時新增數字8進入樹結構。
(4)然後我們新增一個數字6到樹結構中,看一下具體put函式是如何執行的:
那麼此時Comparable<? super K> k = (Comparable<? super K>) key;我們傳入的引數key=6 。
cmp = k.compareTo(t.key); cmp = 6和7對比,6<7返回一個負數,cmp < 0 ,在7的left左邊。此時的樹結構為:
此時左邊還有一個1,在跟1比較,cmp返回一個正數大於0,所以在1的右邊,最終定型為:6在7的左邊在1的右邊。
(5)後面我們新增一個5數字,看一下put函式具體執行:
先看一下目前的root的值資訊:
key = 7, left = 1 right = 8
此時Comparable<? super K> k = (Comparable<? super K>) key; key = 我們傳入的引數 5.
第一次對比:cmp = k.compareTo(t.key); = 5和7對比 5>7返回負數在7的left
第二次對比:cmp = k.compareTo(t.key); = 5和1對比 5>1返回正數在1的right
然後我們看一下1的樹機構:
此時1的樹結構如圖為:
此時由於第二次返回了right在1的右邊有6需要進行第三次對比
第三次對比:cmp = k.compareTo(t.key); = 5和6對比 5>6返回負數在6的left 左邊
此時root中的結構為:
key為根目錄,此時key = 7 left = 5 right = 8 為第一層樹結構:
其中5位跟目錄還有二層樹結構,最終形成樹結構為:
我們讀取資料的時候從遵循一個左中右的讀取順序為:1,5,6,7,8 自然排序為升序。
總結:此過程講解的是TreeSet的自然排序的一個過程以及比對的具體原始碼實現方案,其中有講解錯誤的地方,還評論指出。
其中有key不能為空的原因。後續會在其他文章中講解自定義排序。還有哪些方面需要總結的也歡迎評論指出。