public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
static final long serialVersionUID = -5024744406713321676L;
// HashSet 底層是使用 HashMap 來實現的
// transient 表示序列化的時候不自動寫入,而是手動調用寫入
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
// 做爲 value 存儲於 HashMap 中
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<>();
}
/** * 根據傳入集合設置 map 的初始化容量,默認爲 16,或者是集合大小 * 1.75 * @param c 傳入集合 */
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
// 將集合中全部的數據放入 HashMap 中
addAll(c);
}
/** * 會根據這個 2 個參數去初始化 HashMap 具體去看 HashMap 源碼 * @param initialCapacity 初始化容量 * @param loadFactor 裝載因子 */
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/** * 該方法是給 LinkedHashSet 使用的,具體請看 LinkedHashSet 源碼分析 * @param initialCapacity * @param loadFactor * @param dummy 無實際意義只是爲了區分的重載方法 */
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
/** * HashSet 只能使用迭代進行遍歷,由於它底層的實現是 HashMap 不支持隨機訪問元素, * 因此須要經過 HashMap 的迭代器來進行元素遍歷 * @return */
public Iterator<E> iterator() {
return map.keySet().iterator();
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
/** * 添加元素的時候,它的不包含重複元素是創建在 map 上的 * @param e * @return */
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
/** * HashSet 實現了 Cloneable 接口,支持 clone * clone 一份 HashSet 基礎數據 * clone 一份 HashMap 數據 * 從新組裝返回 * @return */
@SuppressWarnings("unchecked")
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
/** * 序列化寫出方法 * @param s * @throws java.io.IOException */
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
// 寫出非 static 和非 transient 屬性
s.defaultWriteObject();
// 寫出容量和裝載因子
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// 寫出數據長度
s.writeInt(map.size());
// 寫出全部的數據
for (E e : map.keySet())
s.writeObject(e);
}
/** * 序列化讀入方法 * @param s * @throws java.io.IOException * @throws ClassNotFoundException */
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
// 讀入非 static 和非 transient 屬性
s.defaultReadObject();
// 讀出長度,長度 < 0 拋出錯誤
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// 讀出裝載因子,校驗裝載因子的合法性
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// 讀取數據長度,而且校驗合法性
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// 讀出容量大小,若是小於默認值則賦予默認容量大小
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// 建立 HashMap 或者 LinkedHashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// 依次讀取響應數據長度的值放入 map 中
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
/** * 能夠分割的迭代器,主要用於多線程並行迭代時候使用 * @return */
public Spliterator<E> spliterator() {
return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
}
}
複製代碼
總結java
HashSet 經過 HashMap 不具備重複 key 來保證不包含重複元素,若是已經存在對應的 key 則添加失敗數組
能夠存儲爲 null 的元素,由於 HashMap 的 key 能夠爲 null安全
HashSet 是無序的,由於 HashMap 的實現是數組 + 鏈表的形式,而 key 將其 hash 後跟 map.size() - 1 作 &多線程
獲得的數組下標是隨機分佈的,具體細節請看 HashMap 的源碼實現源碼分析
HashSet 未作線程安全同步性能
fail-fast,當使用迭代器訪問元素的時候,該元素被修改或者被刪除了,就會拋ConcurrentModificationException 是經過維護了一個 modCount 變量來實現的,每次修改這個值都會加,而在進行遍歷中會先去檢查這個值發現這個數據和遍歷前記錄的值不一致則拋錯this
若是能事先知道容量大小最好直接指定好容量,由於能夠避免或者減小 HashMap 的擴容次數以提升性能spa