Hashtable 是 JDK 1.0 遺留的線程安全哈希表,雖已被 ConcurrentHashMap 取代,但其同步機制、擴容策略、與 HashMap 差異仍具教學價值。
private transient Entry[] table; // 桶數組
private transient int count; // 鍵值對數量
private int threshold; // 擴容閾值 = capacity * loadFactor
private float loadFactor; // 默認 0.75f
private transient int modCount = 0; // 修改次數,支援 fail-fast
// 默認構造器:容量 11,負載因子 0.75
public Hashtable() { this(11, 0.75f); }
| 維度 | Hashtable | HashMap (JDK8) |
|---|---|---|
| 線程安全 | 所有 public 方法 synchronized | 非線程安全 |
| 默認容量 | 11 (質數) | 16 (2 的冪) |
| 容量要求 | 任意整數 | 必須 2 的冪 |
| Null Key/Value | ❌ 存入拋 NPE | ✅ Key 1個 null,Value 多個 null |
| 哈希計算 | key.hashCode() & 0x7FFFFFFF | hash(key) = h ^ (h >>> 16) |
| 索引定位 | (hash & 0x7FFFFFFF) % length (取模) | (n-1) & hash (位與) |
| 擴容策略 | newCap = oldCap * 2 + 1 (質數傾向) | newCap = oldCap << 1 (保持 2 的冪) |
| 迭代器 | Enumeration / Iterator (fail-fast) | Iterator (fail-fast) |
| 繼承體系 | extends Dictionary | extends AbstractMap |
// 典型同步方法
public synchronized V get(Object key) { ... }
public synchronized V put(K key, V value) { ... }
public synchronized V remove(Object key) { ... }
public synchronized void putAll(Map t) { ... }
// 缺點:鎖粒度粗,整個表鎖住,高併發嚴重爭用
// 替代:Collections.synchronizedMap(new HashMap<>()) 同樣粗粒度
// 最佳:ConcurrentHashMap (分段鎖/CAS 細粒度)
count >= threshold → rehash() 擴容count++,modCount++
protected void rehash() {
int oldCap = table.length;
Entry[] oldMap = table;
int newCap = oldCap * 2 + 1; // 質數傾向,減少碰撞
Entry[] newMap = new Entry[newCap];
threshold = (int)(newCap * loadFactor);
table = newMap;
modCount++;
// 重新散列所有節點
for (int i = oldCap; i-- > 0; ) {
for (Entry e = oldMap[i]; e != null; ) {
Entry next = e.next;
int index = (e.hash & 0x7FFFFFFF) % newCap;
e.next = newMap[index];
newMap[index] = e;
e = next;
}
}
}
僅支援按 Value 查找,遍歷整表 O(n),Value 不能為 null。
// 遷移前
Hashtable table = new Hashtable<>();
table.put("k", "v");
// 遷移後:高併發場景
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("k", "v");
map.computeIfAbsent("k", k -> expensiveCompute());
// 遷移後:單線程/低併發,需同步
Map map = Collections.synchronizedMap(new HashMap<>());
// 復合操作需手動同步:
synchronized(map) {
if (!map.containsKey("k")) map.put("k", compute());
}
Hashtable 以粗粒度同步實現線程安全,犧牲了擴展性。理解其取模定位、質數擴容、同步鎖全表等設計,有助於理解 ConcurrentHashMap 如何通過細粒度鎖、CAS、紅黑樹化解這些問題。新代碼嚴禁使用 Hashtable。