我的jdk源碼(十五):HashSet類

一、概述

    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覺淺】,獲取第一時間更新哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章