HashSet 是 Java 集合框架中最常用的 無序、不重複 集合實現。其核心秘密在於:底層完全委託 HashMap 實現。本文將從源碼級別剖析 HashSet 的結構設計、四大構造器、add/remove/contains 核心方法,並深入講解其去重機制與序列化細節,助你徹底掌握這一基礎組件。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {
// 底層存儲容器
private transient HashMap<E, Object> map;
// 虛擬值,所有元素在 Map 中對應的 value 都是這個對象
private static final Object PRESENT = new Object();
// ...
}
設計精髓:
writeObject/readObject 手動控制,節省空間並保證兼容性。| 構造器 | 底層 HashMap 初始化邏輯 | 適用場景 |
|---|---|---|
HashSet() | new HashMap<>()(默認容量 16,負載因子 0.75) | 通用場景,無特殊性能要求 |
HashSet(Collection<? extends E> c) | new HashMap<>(Math.max((int)(c.size()/.75f)+1, 16)) 並 addAll(c) | 已有集合轉 Set,預估大小避免擴容 |
HashSet(int initialCapacity, float loadFactor) | new HashMap<>(initialCapacity, loadFactor) | 大數據量預知場景,精確控制擴容閾值 |
HashSet(int initialCapacity) | new HashMap<>(initialCapacity)(負載因子默認 0.75) | 只關心初始容量 |
HashSet(int initialCapacity, float loadFactor, boolean dummy) | new LinkedHashMap<>(...)(包私有,供 LinkedHashSet 調用) | 維持插入順序的 LinkedHashSet 專用 |
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
邏輯推演:
map.put(key, value),Key 為元素 e,Value 為 PRESENT。true。false。結論: HashSet.add 的去重完全依賴 HashMap.put 的 Key 唯一性,而 Key 的相等判斷依賴 hashCode() 與 equals() 契約。
public boolean contains(Object o) { return map.containsKey(o); }
public boolean remove(Object o) { return map.remove(o) == PRESENT; }
直接委託 Map 的 containsKey、remove,時間複雜度 O(1)。
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone(); // 淺拷貝 HashMap
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
注意:元素對象本身不拷貝,新舊 Set 共享元素引用。
new HashSet<>(expectedSize),減少 rehash 開銷。Collections.synchronizedSet(new HashSet<>()) 或改用 ConcurrentHashMap.newKeySet()(Java 8+)。ConcurrentModificationException。HashSet 以 HashMap 為底層支撐,通過「Key 存元素、Value 存常量」的極簡設計,實現了 O(1) 級別的增刪查與自動去重。掌握其構造器選擇、hashCode/equals 契約、擴容機制及線程安全邊界,是寫出高性能、無 Bug Java 代碼的基礎功力。