概述api
在分析HashSet源碼前,先看看HashSet的繼承關係安全
HashSet繼承關係
從上圖能夠看出,HashSet繼承自AbstractSet,實現了Set接口,接着看一下源碼中的註釋this
This class implements the Set interface, backed by a hash table
(actually a HashMapinstance). It makes no guarantees as to the
iteration order of the set; in particular, it does not guarantee that the
order will remain constant over time. This class permits the null element.
HashSet實現了Set接口,內部有一個哈希表支撐(實際上就是一個HashMap實例),它不保證迭代的順序;尤爲是,隨着時間的變化,它不能保證set的迭代順序保持不變。容許插入空值。
到此發現,HashSet實際上能夠拆分紅Hash跟Set,Hash指的是HashMap,Set則是指實現了Set接口,這樣看來,HashSet的實現其實就比較簡單了,下面開始分析源碼。spa
正文線程
成員變量code
//序列化ID static final long serialVersionUID = -5024744406713321676L; //內置的HashMap private transient HashMap<E,Object> map; // 就是一個傀儡,填充HashMap的Value而已,沒有實際意義 private static final Object PRESENT = new Object();
構造方法blog
空的構造方法繼承
初始化一個空的HashMap接口
public HashSet() { map = new HashMap<>(); }
帶有容量的構造方法ip
HashMap給定一個容量
public HashSet(int initialCapacity) { map = new HashMap<>(initialCapacity); }
帶有容量跟負載因子的構造方法
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor); }
帶有容量跟負載因子,以及Value類型區分
dummy做爲Value是基本類型跟引用類型,注意此處初始化的是一個LinkedHashMap
HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<>(initialCapacity, loadFactor); }
經過一個集合初始化
public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); }
調用addAll方法
public boolean addAll(Collection<? extends E> c) { boolean modified = false; //循環遍歷 for (E e : c) //若是set中沒有此元素,添加成功 if (add(e)) modified = true; return modified; }
增長元素
添加一個元素,若是Map中存在,返回false,不然返回true
public boolean add(E e) { return map.put(e, PRESENT)==null; }
看一下Map的put方法
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); for (HashMapEntry<K,V> e = table[i]; e != null; e = e.next) { Object k; //這裏比較了hash值跟equals方法 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
因此Set元素必須複寫hashcode跟equals方法,否則會致使元素錯亂
刪除元素
public boolean remove(Object o) { //直接調用map的方法 return map.remove(o)==PRESENT; }
clear
public void clear() {
//調用map的Clear方法
map.clear(); }
contains方法
public boolean contains(Object o) { 調用map的contains方法 return map.containsKey(o); }
isEmpty
public boolean isEmpty() { //調用map的isEmpty方法 return map.isEmpty(); }
迭代
public Iterator<E> iterator() {
//由於不須要value,因此只是調用了keySet的iterator
return map.keySet().iterator(); }
分析了一下,其實最終的底層實現都是在調用HashMap的方法,因此瞭解了HashMap的源碼以後,HashSet其實就會比較簡單了
總結
HashSet是非線程安全的,容許插入空元素HashSet不容許重複元素HashSet的Key須要複寫hashcode跟equals方法