前言
本文使用用的是jdk1.8
Set 接口
我們前面再Collection集合的文章中已經(jīng)簡單的說過Set集合的特點。
- 不包含重復元素,即兩個元素
e1.equals(e2)不可相等。
需要注意的是:
當可變對象存儲到Set集合中的時候,如果通過一種可以影響對象的equals函數(shù)的比較結果的方式改變了該對象的話那么Set集合的行為則是不確定的。
Set 集合的常見實現(xiàn)類

- HashSet:底層是通過HashMap來實現(xiàn)的,所以底層數(shù)據(jù)結構式是 散列表+紅黑樹 的數(shù)據(jù)結構。
- TreeSet:底層數(shù)據(jù)結構是通過NavigableMap來實現(xiàn)的,底層數(shù)據(jù)結構是 紅黑樹??杀WC元素的排列方式
- LinkedHashSet:繼承自HashSet,和LinkedHashMap意義相似,底層數(shù)據(jù)結構是 散列表+雙向鏈表
HashSet
總的來說:
- 實現(xiàn)了Set接口,底層其實是一個HashMap實例,所以由于HashMap的特性,無法保證元素的迭代的順序(HashMap擴容后的重散列會導致元素存儲位置的變更)。同時和HashMap一樣可以使用null元素做鍵值,但只能是一個。
- 對Set結合進行迭代所需的時間與HashSet實例的大小(元素的數(shù)量)和底層HashMap實例(桶的數(shù)量)的“容量”的和成比例。如果某個Set的迭代性能比較重要,不要將初始容量設置的太高,或者將加載因子設置的太小(會出現(xiàn)很多空桶)。
- 非同步,并發(fā)不安全??梢猿绦蛲獠渴謩油?,也可以借助
Collections.synchronizedSet函數(shù)來對該Set集合進行包裝來實現(xiàn)同步。 - 迭代器是快速失敗的
HashSet的屬性及函數(shù)

一眼看上去還是很簡單的。只有兩個屬性,一個是它具體實現(xiàn)類HsahMap,另一個則是一個Object實例。這個Object實例在這里是充當HashMap實例中所有元素的公共value的。因為我們HashSet底層是通過HashMap來實現(xiàn)的,所有元素實際都存儲在key中,那么所對應的value如果不賦值則是null值得存在。所以HashSet的處理方式就是將所有元素的value都賦予該Object實例。
所以整個HashSet整體上來說幾乎所有的操作都是在底層操作了一個HashMap實例。根據(jù)上圖我們來看一下HashSet所實現(xiàn)的Set接口的函數(shù)都是怎么實現(xiàn)的。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public void clear() {
map.clear();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public int size() {
return map.size();
}
所有的構造函數(shù)所做的也是統(tǒng)一的初始化HashMap的操作
public HashSet() {
map = new HashMap<>();
}
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
TreeSet
總的來說:
- 底層實現(xiàn):和HashSet實現(xiàn)方式一樣,底層其實是由一個TreeMap實例來實現(xiàn)的。并實現(xiàn)了NavigableSet接口,是一個基于TreeMap的NavigableSet的實現(xiàn)
-
有序:由于實現(xiàn)的NavigableSet接口繼承自SortedSet接口,所以存入TreeSet的所有元素都必須是實現(xiàn)了Comparable接口的,即保證這些元素在存入TreeSet的時候在通過
compareTo函數(shù)進行比較并自然排序的時候不會報ClassCastException?;蛘咭部梢栽赟et實例構造的時候通過傳入的 Comparator來進行控制排序。 -
非同步:并發(fā)線程不安全。還是老樣子,倘若想同步需要函數(shù)外手動進行控制,或者在創(chuàng)建Set集合的時候借助
Collections類。Collections.synchronizedSortedSet的方法來對某個Set集合進行包裝。 - 迭代器:迭代器是快速失敗的
-
復雜度:基本操作函數(shù)如
add,remove,contains的復雜度都是log(n)級別的(TreeMap底層紅黑樹)。
TreeSet的屬性和函數(shù)

從圖可以很明顯的看出它的實現(xiàn)機制和HashSet是一模一樣的。底層通過NavigableMap來實現(xiàn),而NavigableMap則只有TreeMap這一個實現(xiàn)類。我們也可以稱之為底層是由TreeMap來實現(xiàn)的,而Object實例則是充當所有元素的value的存在。

我們來看一下TreeSet的5個構造函數(shù)都是如何實現(xiàn)的
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
還是清晰的。都是通過構造一個TreeMap來實現(xiàn)整個TreeSet的所有操作的。
LinkedHashSet
總的來說:
- 實現(xiàn):LinkedHashSet底層由散列表和雙向鏈表來實現(xiàn)的。這一點和LinkedHashMap一樣。LinkedHashSet由HashSet和LinkedList共同來實現(xiàn)的。底層是HashMap,只是各節(jié)點Entry另外的還添加了兩個屬性before和after,通過雙向列表來鏈接。
- 迭代:迭代有序,由于所有元素都通過LinkedeList雙向鏈表來維護,所以將按照元素插入到set中的順序(插入順序)進行迭代。需要注意的是如果一個元素已經(jīng)存在于LinkedHashSet中如果再插入該元素的話,該元素會被重新插入。并且迭代的時候和HashSet容量沒有關系。因為它結構中維護的一個雙向鏈表,迭代的時候是迭代這個雙向鏈表。相比于此HashSet迭代時則要受到容器容量很大的限制。
-
優(yōu)點
- 使用LinkedHashSet可以不用像HashSet一樣元素雜亂無序,而保證元素一定的有序性,可以做到TreeSet的功能卻不用TreeSet的成本。
- 初始容量的大小對LinkedHasSet的影響要比對HashSet小很多,因為它在迭代的時候不受容器大小的影響。
-
缺點
- 性能比HashSet要差一點兒,因為還要維護一個雙向鏈表。
- 允許null。非同步的,迭代是快速失敗的
LinkedHashSet的屬性和函數(shù)

很純粹的類,沒有自己實現(xiàn)的東西。所有的函數(shù)都是繼承自各個父類
我們來看一下它的構造函數(shù):
public LinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor, true);
}
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
public LinkedHashSet() {
super(16, .75f, true);
}
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
可以看出,都是去構造其父類HashSet。
總結
可以看到整個Set集合的實現(xiàn)都是Map映射。
- HashSet:無序,允許為null,底層是HashMap(散列表+紅黑樹),非線程同步
- TreeSet:有序,不允許為null,底層是TreeMap(紅黑樹),非線程同步
- LinkedHashSet:迭代有序,允許為null,底層是HashMap+雙向鏈表,非線程同步