HashSet原始碼分析,基於JDK1.8詳細分析
阿新 • • 發佈:2021-05-02
閱讀本文章之前推薦先閱讀博主關於HashMap的文章:
HashMap原始碼分析 + 面試題
HashSet原始碼分析
文章目錄
一、基本介紹
-
底層實現基於 HashMap,所以迭代時不能保證按照插入順序或者其它順序進行迭代
-
add、remove、contanins、size 等方法的耗時效能,是不會隨著資料量的增加而增加的,這個主要跟 HashMap 底層的資料結構有關,不管資料量多大,不考慮 hash 衝突的情況下,時間複雜度都是 O (1)
-
執行緒不安全的,允許null值
-
迭代過程中,如果資料結構被改變,會快速失敗,丟擲
ConcurrentModificationException
異常 -
繼承體系
HashSet並沒有繼承HashMap,所以HashSet是通過呼叫HashMap的方法從而使用HashMap,這種不採用繼承而採用組合的方式的優點如下:
- 繼承表示父子類是同一個事物,而 Set 和 Map 本來就是兩種事物,所以繼承不妥,而且 Java 語法限制子類只能繼承一個父類,後續難以擴充套件
- 組合更加靈活,可以任意的組合現有的基礎類,並且可以在基礎類方法的基礎上進行擴充套件、編排等,而且方法命名可以任意命名,無需和基礎類的方法名稱保持一致
二、原始碼分析
1. 成員變數
//把HashMap組合進來,key是Hashset的key,value是下面的PRESENT
private transient HashMap<E,Object> map;
//HashMap中的虛擬value
private static final Object PRESENT = new Object();
觀察上述原始碼,可以得出如下結論:
- 使用 HashSet 時,比如 add 方法,只有一個入參,但組合的 Map 的 add 方法卻有 key、value 兩個入參,Map 的 key 就是 add 的入參,value 就是上述程式碼中的第二個屬性 PRESENT,此處設計非常巧妙,用一個預設值 PRESENT 來代替 Map 的 Value
- 也就是說這個map中所有的value都是一樣的
- 當多個執行緒訪問HashSet時,就會有執行緒安全問題,因為在後續的所有操作中,並沒有加鎖
2. 構造方法
//底層呼叫HashMap的構造器
public HashSet() {
map = new HashMap<>();
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/**
* 如果給定引數集合的初始容量小於16 ,就按照HashMap預設的16初始化即可
* 如果大於16,就按照指定的值進行初始化
* 指定的值 = 引數集合容量 / 0.75 + 1,可以使得期望的值正好比擴容的閥值還大1,就不會擴容,符合HashMap擴容的公式,可以減少擴容次數,提高效率
*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
//以後的開發過程中,如果要給HashMap中拷貝集合,HashMap的初始化大小可以借鑑這種寫法
//非public,只能被同一個包呼叫,這是LinkedHashSet專屬的構造器
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
//建立的是LinkedHashMap,非HashMap
}
3. 新增方法
add(E e)
方法原始碼如下:
public boolean add(E e) {
//直接呼叫HashMap的put()方法,把元素本身作為key,把PRESENT作為value
return map.put(e, PRESENT)==null;
}
4. 刪除方法
remove(Object o)
方法原始碼如下:
public boolean remove(Object o) {
//直接呼叫HashMap的remove()方法
return map.remove(o)==PRESENT;
}
注意,Map的 remove
方法返回的是刪除元素的value,而Set的 remove
方法返回的是boolean型別,如果是null的話說明沒有該元素,如果不是null肯定等於PRESENT
5. 查詢方法
contains(Object o)
方法原始碼如下:
public boolean contains(Object o) {
//呼叫map的containsKey()方法
return map.containsKey(o);
}
6. 遍歷方法
iterator()
方法原始碼如下:
public Iterator<E> iterator() {
//呼叫map的keySet的迭代器
return map.keySet().iterator();
}
三、總結
(1)HashSet內部使用HashMap的key儲存元素,以此來保證元素不重複
(2)HashSet是無序的,因為HashMap的key是無序的
(3)HashSet中允許取值為null的元素,因為HashMap允許key為null
(4)HashSet是非執行緒安全的
(5)HashSet沒有 get()
方法,查詢方法是 contains()
方法