上篇文章《HashMap其實就那麼一回事兒之源碼淺析》介紹了hashMap, 本次將帶你們看看HashSet, HashSet其實就是基於HashMap實現, 所以,熟悉了HashMap, 再來看HashSet的源碼,會以爲極其簡單。下面仍是直接看源碼吧:html
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的源碼做一個總結吧:java
1. HashSet基於HashMap實現, 以HashSet的值做爲HashMap的一個key, 以一個Object對象常量做爲HashMap的值。ide
2. 根據HashMap的特性,能夠推敲出:HashSet容許擁有1個爲null的值, HashSet的值不可重複。post
3. 在建立HashSet的時候,若是合適,最好指定其內部HashMap的 capacity和loadFactory的值, 至於緣由,在介紹HashMap的時候,提到過。測試
OK, 講完HashSet以後,我以爲是時候提一下這個問題了: 可能在你們初學java的時候,老師或者書上都推薦你們在重寫對象equals的時候,最好重寫一下hashCode方法,還記得吧? 爲何要這麼作? 給你們演示一下,你就能明白了,下面看一個小demo:this
先定義一個Person類:url
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實例:spa
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方法:code
@Override public int hashCode() { return this.idCard.hashCode() * 11; }
這時候,咱們再用上面的測試類測試,發現輸出爲1。OK,終於和咱們的想法一致了。這就是爲何強烈推薦在重寫equals方法的時候,同時重寫hashCode方法的緣由之一。htm
好了,本次就寫到此。謝謝你們!