1. 程式人生 > 其它 >HashSet原始碼分析,基於JDK1.8詳細分析

HashSet原始碼分析,基於JDK1.8詳細分析

閱讀本文章之前推薦先閱讀博主關於HashMap的文章:
HashMap原始碼分析 + 面試題

HashSet原始碼分析

文章目錄

一、基本介紹

  • 底層實現基於 HashMap,所以迭代時不能保證按照插入順序或者其它順序進行迭代

  • add、remove、contanins、size 等方法的耗時效能,是不會隨著資料量的增加而增加的,這個主要跟 HashMap 底層的資料結構有關,不管資料量多大,不考慮 hash 衝突的情況下,時間複雜度都是 O (1)

  • 執行緒不安全的,允許null值

  • 迭代過程中,如果資料結構被改變,會快速失敗,丟擲 ConcurrentModificationException 異常

  • 繼承體系

    image-20210428092020110

    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() 方法