一、概述
HashSet類是常用的Set集合類,底層是採用HashMap來實現的,我們通常利用HashSet類的元素不重複的特性來達到去重的效果,那麼跟隨源碼去一探究竟吧。
二、源碼分析
(1) 類的聲明
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
在類的聲明中我們可以看到,HashSet類是繼承自AbstractSet類,HashSet類可以複用父類的一些方法,同時HashSet類還是實現了Set接口,以及Cloneable接口和Serializable。
* AbstractSet繼承自AbstractCollection,實現了Set接口,此類並沒有重寫 AbstractCollection 類中的任何實現(包括add()方法)。它僅僅添加了 equals 和 hashCode 的實現。
* 實現了Set接口表示本類是一個不包含重複元素的類,並最多包含一個null值。
* 實現Cloneable接口表示可以調用Object.clone方法返回該對象的淺拷貝。
* 實現Serializable接口表示可以啓用其序列化功能,能通過序列化去傳輸。
(2) 成員變量
//序列化標記ID
static final long serialVersionUID = -5024744406713321676L;
//實際存放元素的map,類型是HashMap,所以HashSet是依賴HashMap的
private transient HashMap<E,Object> map;
//final修飾的不可改變的空對象
private static final Object PRESENT = new Object();
底層通過HashMap來進行數據的存儲,通過下面的源碼可以得知採用HashMap的key來存儲元素,value統一存儲一個不可改變的空對象PRESENT,這樣既保證HashMap的存儲結構,數據的移除操作也是通過HashMap的remove方法進行移除,而HashMap的remove方法會返回被移除的Value值,而把PRESENT設爲final就能保證可以通過判斷返回的值是否是PRESENT對象就可以判斷是否移除成功。
(3) 構造方法
//無參構造函數
public HashSet() {
//初始化map爲一個空的HashMap
map = new HashMap<>();
}
//帶容量的構造函數
public HashSet(int initialCapacity) {
//初始化一個容量參數爲initialCapacity的HashMap,但是最後map的容量不一定是initialCapacity,而是大於等於initialCapacity的最小2次冪
map = new HashMap<>(initialCapacity);
}
//帶容量和負載因子的構造函數
public HashSet(int initialCapacity, float loadFactor) {
//在上一個方法基礎上,多了設置負載因子這一步
map = new HashMap<>(initialCapacity, loadFactor);
}
//帶容量和負載因子以及標記dummy的構造函數,但是沒有public修飾。dummy標記是用來區分HashSet(int initialCapacity, float loadFactor)方法
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
//傳入一個集合c的構造函數
public HashSet(Collection<? extends E> c) {
//創建一個足夠大的map,容量計算規則是取(c.size()/.75f)+1和16取較大的值作爲initialCapacity,但是這也不是最終的容量,最終的容量還是大於initialCapacity的最小2次冪
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
//添加集合c中的所有元素到map中
addAll(c);
}
(4) add()方法
//添加一個元素
public boolean add(E e) {
//調用HashMap的put方法,因爲HashMap的Key不能重複,重複時會把添加的元素直接返回,成功則返回null
return map.put(e, PRESENT)==null;
}
HashSet類爲什麼能去重呢?它的add()方法告訴了我們答案,HashSet在調用set()方法的時候,是把添加的元素作爲了HashMap的key,而value就是上面final修飾的空對象PRESENT。我看到這兒我驚了, HashSet利用HashMap的key不能重複的機制,因爲key重複就覆蓋value,以此來達到去重的效果,我只能說絕了!
(5) 其餘核心方法
//調用HashMap的clear方法來清空元素
public void clear() {
map.clear();
}
//調用HashMap的containsKey來判斷是否包含元素o
public boolean contains(Object o) {
return map.containsKey(o);
}
//通過調用HashMap的isEmpty方法來判斷集合是否爲空
public boolean isEmpty() {
return map.isEmpty();
}
//返回迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
}
//調用HashMap的remove方法來移除元素
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
//HashMap的大小就是集合的大小
public int size() {
return map.size();
}
HashSet類的其他方法也比較簡單,都是直接調用HashMap的方法,如果你對HashMap還不熟,那我推薦你看一下《我的jdk源碼(十三):HashMap 一磕到底,追根溯源!》。
三、總結
HashSet類還是有幾點比較重要的知識點,羅列如下:
* HashSet底層依賴HashMap,所以也是線程不安全的。
* HashSet底層依賴HashMap,所以支持動態擴容,但也因此和HashMap一樣,無法保證元素的有序性。
* 我們可以利用HashSet的元素不重複特性來達到爲集合去重的效果。
更多精彩內容,敬請掃描下方二維碼,關注我的微信公衆號【Java覺淺】,獲取第一時間更新哦!