本博客將從源碼的角度帶領大家學習TreeSet相關的知識。
一TreeSet類的定義:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
可以看到TreeSet是繼承自AbstracSet同時實現了NavigableSet,Cloneable,Serializable三個接口,其中Cloneable,Serializable這兩個接口基本上是java集合框架中所有的集合類都要實現的接口。二TreeSet中的重要屬性:
private transient NavigableMap<E,Object> m;
private static final Object PRESENT = new Object();
可以看到第一個屬性是NavigableMap接口,該接口是TreeMap的父接口,即TreeMap實現了該接口,據此我們可以推測TreeSet的底層是基於TreeMap的,這一點稍後我們將在TreeSet的構造器中更清楚的看到。第二個參數是以Object對象,與HashSet中的這個屬性完全相同,即TreeSet雖然底層是基於TreeMap的,但是同樣只是用來保存Key而Value值全部爲默認值PRESENT。三TreeSet內部實現原理:我們來看一下TreeSet的構造器:
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
可以看到共5個構造器,其中第一個構造器是私有的,即對外不公開的,而在第二個默認的無參數的構造器中調用了第一個構造器,且可以清楚的看到在這個構造器中創建了一個TreeMap對象作爲參數傳給第一個構造器,這說明我們上面的推測:TreeSet底層是基於TreeMap的是正確的。另外餘下的幾個構造器中可以看到當用一個集合作爲參數去構造一個TreeSet的時候,都是調用addAll這個方法,我們來看一下其源碼:
public boolean addAll(Collection<? extends E> c) {
// Use linear-time version if applicable
if (m.size()==0 && c.size() > 0 &&
c instanceof SortedSet &&
m instanceof TreeMap) {
SortedSet<? extends E> set = (SortedSet<? extends E>) c;
TreeMap<E,Object> map = (TreeMap<E, Object>) m;
Comparator<?> cc = set.comparator();
Comparator<? super E> mc = map.comparator();
if (cc==mc || (cc != null && cc.equals(mc))) {
map.addAllForTreeSet(set, PRESENT);
return true;
}
}
return super.addAll(c);
}
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
可以看到在addAll方法中首先會判斷是否傳入的集合參數c是否爲SortedSet或其子類且c不爲空(c.size()>0),如果是則會調用addAllForTreeSet方法,否則會直接返回addAll方法的結果,關於addAll方法請參看我的博客【java集合框架源碼剖析系列】java源碼剖析之HashSet相關內容,因爲內容相同,在此不做贅述,重點來看一下addAllForTreeSet方法:
void addAllForTreeSet(SortedSet<? extends K> set, V defaultVal) {
try {
buildFromSorted(set.size(), set.iterator(), null, defaultVal);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
private void buildFromSorted(int size, Iterator<?> it,
java.io.ObjectInputStream str,
V defaultVal)
throws java.io.IOException, ClassNotFoundException {
this.size = size;
root = buildFromSorted(0, 0, size-1, computeRedLevel(size),
it, str, defaultVal);
}
可以看到在addAllForTreeSet方法中調用了buildFromSorted(int size, Iterator<?> it, java.io.ObjectInputStream str,V defaultVal),該方法的作用即是在線性時間內對數據進行排序(Linear time tree building algorithm from sorted data),看到這裏我們就明白TreeSet排序的原理了,即當使用一個TreeMap集合作爲參數構造一個TreeSet的時候,TreeSet會將Map中的元素先排序,然後將排序後的元素add到TreeSet中。也就是說TreeSet中的元素都是排過序的,另外正因爲存在排序過程,所以TreeSet不允許插入null值,因爲null值不能排序。四TreeSet中的重要方法:
<strong> </strong>public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return m.remove(o)==PRESENT;
}
public void clear() {
m.clear();
}
public boolean contains(Object o) {
return m.containsKey(o);
}
public E first() {
return m.firstKey();
}
public E last() {
return m.lastKey();
}
可以看到TreeSet中與TreeMap中同名的方法全部都是調用的TreeMap中的方法來實現的,其中add方法在調用TreeMap的put方法時第二個參數傳入的是固定值PRESENT,一個Object類型對象。五總結:經過前面TreeMap的源碼剖析可以看到TreeSet非常簡單
1TreeSet底層是基於TreeMap的(而TreeMap是基於紅黑樹的),但是僅僅用來保存Key,而不保存Value,因爲TreeSet的add()方法在調用TreeMap的put方法的時候第二個參數傳入的都是PRESENT這個固定的Object對象。
2可以看到TreeSet中的add與remove等方法均無synchronized關鍵字修飾,即TreeSet不是線程安全的,如果要使用同步的TreeSet需要使用Collections集合類的靜態方法,即Set s=Collections.synchronizedSet(new TreeSet());
3TreeSet中的元素是自動排好序的,插入的值不允許爲null。
4TreeSet中元素的值必須是唯一的,因爲TreeSet底層是基於TreeMap的,而TreeMap不允許元素key重複。