HashSet其實就那么一回事兒之源碼淺析


上篇文章《HashMap其實就那么一回事兒之源碼淺析》介紹了hashMap,  本次將帶大家看看HashSet, HashSet其實就是基於HashMap實現, 因此,熟悉了HashMap, 再來看HashSet的源碼,會覺得極其簡單。下面還是直接看源碼吧:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    //HashMap ? 沒錯,HashSet就是通過HashMap保存數據, HashSet的值就是HashMap的key
    private transient HashMap<E,Object> map;
    
    //HashMap 為<key, value>的鍵值對, 既然HashSet的值就是HashMap的key, 那么HashMap的值呢,當然就是這個PRESENT啦
    private static final Object PRESENT = new Object();
    
    //下面這一系列的構造方法都是創建HashMap, 之前已經介紹過HashMap, 這兒就不再詳說了
    public HashSet() {
        map = new HashMap<>();
    }

    //將一個已知的collection轉換為HashSet
    public HashSet(Collection<? extends E> c) {
        //這兒的HashMap的參數為什么這么寫?
        //上次介紹HashMap的時候可知,如果沒有指定HashMap的capacity, 那么默認的就是16
        //根據 threshold = capacity * loadFactor, 可以計算出 capacity
        //Math.max((int) (c.size()/.75f) + 1, 16) 這個意思就是capacity如果沒超過16, 那么就直接使用默認的16
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        //將已知的collection轉換為HashSet的方法
        //addAll方法是HashSet的父類AbstractCollection的方法,為了便於閱讀,會將代碼粘貼在下面
        addAll(c);
    }

    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }


    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }


    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    
    //addAll方法是HashSet的父類AbstractCollection的方法
    public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            //此處的add方法由HashSet重寫實現
            if (add(e))
                modified = true;
        return modified;
    }
    
    //HashSet的核心方法來了, 沒錯,就這么簡單
    public boolean add(E e) {
        //應證了上面所說的key為HashSet的值
        return map.put(e, PRESENT)==null;
    }
    
    //剩下這些方法都是跟Map相關的了,只要熟悉了HashMap, 那就太簡單了,就不說了
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    public void clear() {
        map.clear();
    }
    
}

就這樣,HashSet的源碼如此簡單。下面還是對HashSet的源碼作一個總結吧:

1. HashSet基於HashMap實現, 以HashSet的值作為HashMap的一個key, 以一個Object對象常量作為HashMap的值。

2. 根據HashMap的特性,可以推敲出:HashSet允許擁有1個為null的值, HashSet的值不可重復。

3. 在創建HashSet的時候,如果合適,最好指定其內部HashMap的 capacity和loadFactory的值, 至於原因,在介紹HashMap的時候,提到過。

 

OK, 講完HashSet之后,我覺得是時候提一下這個問題了: 可能在大家初學java的時候,老師或者書上都推薦大家在重寫對象equals的時候,最好重寫一下hashCode方法,還記得吧? 為什么要這么做? 給大家演示一下,你就能明白了,下面看一個小demo:

先定義一個Person類:

public class Person {

    //身份證
    private String idCard;
    
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public String getIdCard() {
        return idCard;
    }

    public void setIdCard(String idCard) {
        this.idCard = idCard;
    }

    //重寫equals方法(規則是:idCard一致,則認為是同一個人)
    @Override
    public boolean equals(Object obj) {
        if(obj == this) {
            return true;
        }
        if(!(obj instanceof Person)) {
            return false;
        }
        Person others = (Person) obj;
        if(others.getIdCard().equals(idCard)) {
            return true;
        }
        return false;
    }
}

然后,寫一個測試類,用HashSet去添加Person實例:

public class Test {

    public static void main(String[] args) {
        
        Person p1 = new Person();
        p1.setIdCard("1234567890");
        
        Person p2 = new Person();
        p2.setIdCard("1234567890");
        
        Set<Person> hashSet = new HashSet<Person>();
        hashSet.add(p1);
        hashSet.add(p2);
        
        System.out.println(hashSet.size());
        
    }
    
}

我們知道HashSet的元素不可重復,因此,在以上測試代碼中,p1 與 p2對象是equals的,我們本來希望HashSet只保存其中一個對象, 但是,事與願違,輸出的結果卻是2, 說明hashSet把這兩個對象都保存了。這是為什么呢? 我們結合一下HashMap來看吧, 首先,由於我們沒有重寫Person的hashCode方法,會導致p1 與 p2的hash值不一致,這時, HashMap會把hash不一致的元素放在不同的位置, 因此就產生了兩個對象。那么,怎么改善? 當然是重寫hashCode方法了。下面,我們在Person類中,重寫hashCode方法:

@Override
    public int hashCode() {
        return this.idCard.hashCode() * 11;
    }

這時候,我們再用上面的測試類測試,發現輸出為1。OK,終於和我們的想法一致了。這就是為什么強烈推薦在重寫equals方法的時候,同時重寫hashCode方法的原因之一。

好了,本次就寫到此。謝謝大家!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM