HashSet<E> 是 Java 集合框架中 Set 接口最常用的實現,其核心特徵是無序、唯一、允許單一 null。本文從 JDK 源碼(1.8 版本為準)出發,深度解析其「以 Map 為底層」的精妙設計,揭示為何它能高效支撐去重與查找操作。
private transient HashMap<E, Object> map;
// 所有元素共用的唯一 VALUE 對象
private static final Object PRESENT = new Object();
關鍵洞察:HashSet 並不單獨維護數據結構,而是完全委託給一個 HashMap<E, Object>。所有「元素」實際存儲為 Map 的 Key,而 Value 統一指向同一個 PRESENT 靜態對象。這種「鍵值統一」設計極大簡化了實現複雜度。
| 構造器 | 底層 HashMap 參數 | 適用場景 |
|---|---|---|
HashSet() | 默認容量 16、負載因子 0.75 | 通用場景 |
HashSet(Collection<? extends E> c) | 容量 = max((int)(c.size()/0.75f)+1, 16) | 批量導入,預估大小避免擴容 |
HashSet(int initialCapacity) | 指定容量、默認負載因子 | 預知數據量大致範圍 |
HashSet(int initialCapacity, float loadFactor) | 完全自定義 | 極致性能調優 |
HashSet(int, float, boolean dummy) | LinkedHashMap(包私有) | 支援 LinkedHashSet 維持插入序 |
// 添加元素:利用 put 返回舊值特性實現去重
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// 判斷包含:直接委託 containsKey
public boolean contains(Object o) {
return map.containsKey(o);
}
// 刪除元素:remove 返回被刪除的 Value,比對 PRESENT
public boolean remove(Object o) {
return map.remove(o) == PRESENT;
}
// 迭代器:直接返回 keySet 迭代器
public Iterator<E> iterator() {
return map.keySet().iterator();
}
由於 HashMap 的 table 字段為 transient,HashSet 實現了自定義 writeObject/readObject:
LinkedHashSet),逐個 put(e, PRESENT)這保證了反序列化後仍保持去重特性與正確的內部結構。
resize() 導致性能抖動hashCode()/equals(),是正確性的前提LinkedHashSet(雙向鏈表維序)或 TreeSet(紅黑樹有序)透過源碼可以看到,HashSet 是「組合優於繼承」的教科書級範例:用極少代碼復用 HashMap 成熟穩健的哈希邏輯,實現了 Set 接口的全部契約。下次面試被問「HashSet 如何保證唯一性」,你可以自信地回答:「它把元素當 Key 存進 HashMap,Value 統一用一個 PRESENT 對象佔位,put 返回 null 代表首次加入,非 null 代表已存在。」