前面分析了Java
中的常見五大map
容器,Java
中的Set
容器是對Map
容器的封裝,今天就結合源碼分析一下Java
中常見的三大Set
容器。
註明
:以下源碼分析都是基於jdk 1.8.0_221
請先閱讀一下我之前寫的HashMap
、LinkedHashMap
、TreeMap
源碼分析博客,Java
中的Set
就是把Map
改了個名字,這裏不會再次細講Map
的相關內容。
Java容器之HashMap源碼分析(媽媽再也不用擔心我不懂HashMap了)
Java容器之LinkedHashMap源碼分析(看看確定不點進來?進來真不點?)
Java容器之TreeMap源碼分析(附紅黑樹調整圖解,全網最詳細、圖解最全,不服來辯)
Java容器之HashSet、LinkedHashSet、TreeSet源碼分析目錄
一、Set
容器概述
Set
,也就是我們稱的集合
,與數學中的集合
在概念上基本一致。數學中的集合中的元素具確定性
(不存在既屬於又不屬於的集合的元素)、無序性
、互異性
(集合中的元素沒有相同的多個),Java
中的Set
同樣具有確定性
、互異性
,但是有序
、無序
的Set
都有實現。HashSet
集合是完全無序的(內部根據元素的hash值進行散列),LinkedHashSet
是插入順序,TreeSet
是元素自身大小順序。
Java
中的HashSet
、LinkedHashSet
、TreeSet
類的關係如下:
二、HashSet
1、HashSet
容器概述
HashSet
類的聲明如下:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
Java
中的HashSet
容器是封裝了一個HashMap
,元素都是存放到HashMap
中,存放的形式是key-value
,其中元素作key,空的Object
對象作value
。
下圖是JDK 1.8中的HashMap
容器底層數據結構示意圖。
2、HashSet
類的主要屬性
由於元素的存取都交給了HashMap
,所以HashSet
非常簡單,只有兩個比較重要的屬性。
// 用於存放元素
private transient HashMap<E,Object> map;
// 由於map是存儲key-value形式的數據,所以存入的元素作key,value一律用PRESENT替代
private static final Object PRESENT = new Object();
3、HashSet
類的構造器
/**
* 默認構造器(調用HashMap的默認構造器,table數組默認長度16,負載因子默認0.75)
*/
public HashSet() {
map = new HashMap<>();
}
/**
* 複製構造器
* 將容器C中的元素複製到當前創建的set容器中
*/
public HashSet(Collection<? extends E> c) {
// c.size()/.75f) + 1的作用是計算最小需要的容量,0.75是默認負載因子
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
// copy到當前初始化的set容器
addAll(c);
}
/**
* @param initialCapacity hashmap中的table數組長度
* @param loadFactor 負載因子(initialCapacity * loadFactor 表示 map實際可存放的最大key-value數,超過就需要進行擴容)
*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* 默認負載因子0.75
* @param initialCapacity hashmap中的table數組長度table
*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/**
* 此構造器將map初始化爲一個LinkedHashMap(是HashMap的子類,可維持key-value插入的順序)
* 注意:此構造器未使用任何修飾符修飾,默認是default,也就是在當前包下可用
* 後面講到LinkedHashSet時,會提及到這個構造方法
*
* @param initialCapacity LinkedHashMap中的table數組長度
* @param loadFactor 負載因子(initialCapacity * loadFactor 表示 map實際可存放的最大key-value數,超過就需要進行擴容)
* @param dummy ignored (distinguishes this
*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
4、HashSet
實現原理分析
現在我們看看HashSet
中的查找、插入、刪除元素是不是調用了HashMap
的相關方法。
①、add
方法
/**
* 插入一個元素到set中,如果插入成功返回true,插入失敗(已經包含這個元素)返回false
*/
public boolean add(E e) {
// 調用hashmap.put方法進行插入,key = e,value = PRESENT(前面介紹過了,HashSet類中的常量,空Object對象)
return map.put(e, PRESENT)==null;
}
②、contains
方法
/**
* 查找元素
*/
public boolean contains(Object o) {
// 插入時,元素作key,此時查找只需要查找key是否存在即可
return map.containsKey(o);
}
③、remove
方法
/**
* 刪除元素,刪除成功返回true,刪除失敗(容器不再此元素)返回false
*/
public boolean remove(Object o) {
// 調用hashmap中的remove刪除key-value
return map.remove(o)==PRESENT;
}
三、LinkedHashSet
1、LinkedHashSet
容器概述
LinkedHashSet
類的聲明如下:
public class LinkedHashSet<E>
extends HashSet<E>
implements Set<E>, Cloneable, java.io.Serializable
LinkedHashSet
是HashSet
的子類,但是初始化HashSet.map
容器時,用的是LinkedHashMap
容器。LinkedHashMap
是HashMap
的子類,LinkedHashMap
底層也是使用HashMap存放key-value
,但是維護了一個雙鏈表,特性是可按插入順序存儲。
因此LinkedHashSet
與HashSet
的不同是,HashSet
中存儲的元素是無序的,LinkedHashSet
中的元素是按插入順序
存儲的。
2、LinkedHashSet
類的主要屬性
LinkedHashSet
類除了serialVersionUID
(序列化id)沒有自己特有屬性,根據繼承規則,它繼承有HashSet
類中的所有屬性。
3、LinkedHashSet
類的構造器
/**
* 指定LinkedHashMap的容量、負載因子
*/
public LinkedHashSet(int initialCapacity, float loadFactor) {
// 這裏調用HashMap的構造器,HashMap、LinkedHashSet在同一包下
// 所以可以調用HashSet(int initialCapacity, float loadFactor, boolean dummy)構造器(在HashMap中特意提及過)
super(initialCapacity, loadFactor, true);
}
/**
* 默認負載因子爲0.75
*/
public LinkedHashSet(int initialCapacity) {
super(initialCapacity, .75f, true);
}
/**
* LinkedHashMap默認16,負載因子默認0.75
*/
public LinkedHashSet() {
super(16, .75f, true);
}
/**
* 複製構造器,將容器c中的元素全部複製到新建的容器中
*/
public LinkedHashSet(Collection<? extends E> c) {
super(Math.max(2*c.size(), 11), .75f, true);
addAll(c);
}
3、LinkedHashSet
實現原理分析
LinkedHashSet
還真沒啥好分析的,作爲HashSet
的子類,本身未重寫任何查找、插入、刪除相關的方法,全部調用父類HashSet
中的實現。
關於LinkedHashMap
如何維護插入順序的鏈表,請參考我之前寫的LinkedHashMap
源碼分析,鏈接→Java容器之LinkedHashMap源碼分析(看看確定不點進來?進來真不點?)
四、TreeSet
1、TreeSet
容器概述
Java
中的TreeSet
類聲明如下:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
Java
中的TreeSet
容器是對TreeMap
的封裝,底層使用TreeMap
存放元素。TreeMap
容器底層是紅黑樹,存放key-value形式的數據,所以TreeSet
中的元素作key,一個空Object對象作value。
2、TreeSet
類的主要屬性
同樣由於元素的存儲都交給了TreeMap
,所以TreeSet
類中的主要屬性只有兩個,非常簡潔。
/**
* 存放key-value的TreeMap容器
* TreeMap是NavigableMap的實現類
*/
private transient NavigableMap<E,Object> m;
// key-value形式中的value
private static final Object PRESENT = new Object();
3、TreeSet
中的構造器
/**
* 可指定NavigableMap容器
*/
TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
/**
* new TreeMap對象,調用上面的構造器
*/
public TreeSet() {
this(new TreeMap<E,Object>());
}
/**
* 手動指定元素的比較器(key的排序方式)
*/
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
/**
* 複製構造器
*/
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
/**
* 複製構造器
*/
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
4、TreeSet
實現原理分析
TreeSet
重寫了查找、插入、刪除的相關方法,不過只是對TreeMap
的相關方法就行封裝。
①、add
方法
/**
* 插入一個元素到set中,如果插入成功返回true,插入失敗(已經包含這個元素)返回false
*/
public boolean add(E e) {
// 調用hashmap.put方法進行插入,key = e,value = PRESENT(前面介紹過了,HashSet類中的常量,空Object對象)
return map.put(e, PRESENT)==null;
}
②、contains
方法
/**
* 查找元素
*/
public boolean contains(Object o) {
// 插入時,元素作key,此時查找只需要查找key是否存在即可
return map.containsKey(o);
}
③、remove
方法
/**
* 刪除元素,刪除成功返回true,刪除失敗(容器不再此元素)返回false
*/
public boolean remove(Object o) {
// 調用hashmap中的remove刪除key-value
return map.remove(o)==PRESENT;
}
關於TreeMap是如何維護紅黑樹
的,請參考我之前的TreeMap
源碼分析博客,鏈接→Java容器之TreeMap源碼分析(附紅黑樹調整圖解,全網最詳細、圖解最全,不服來辯)
五、總結
Java
中的Set
是對Map
容器的封裝,主要是因爲Map
支持key-value
的存儲,滿足Set
集合的特性,Set
支持元素的存儲,所以只需要實現Map
相關容器,再封裝一下就是Set
容器了。