概述
在分析HashSet原始碼前,先看看HashSet的繼承關係
從上圖可以看出,HashSet繼承自AbstractSet,實現了Set介面,接著看一下原始碼中的註釋
- 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的實現其實就比較簡單了,下面開始分析原始碼。
正文
成員變數
//序列化ID
static final long serialVersionUID = -5024744406713321676L;
//內建的HashMap
private transient HashMap<E,Object> map;
// 就是一個傀儡,填充HashMap的Value而已,沒有實際意義
private static final Object PRESENT = new Object();複製程式碼
構造方法
空的構造方法
初始化一個空的HashMap
public HashSet() {
map = new HashMap<>();
}複製程式碼
帶有容量的構造方法
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方法