从源码角度说明 Hashtable 与 HashMap 的区别?¶
结论¶
两者虽然都是"数组 + 链表"结构,但在哈希算法、初始化、扩容、锁、null 处理上差异明显。
1. 哈希算法不同¶
HashMap(JDK8)¶
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// 下标 = (n - 1) & hash
扰动函数:高 16 位与低 16 位异或,让高位也参与下标计算,减少碰撞。
Hashtable¶
直接用 hashCode(),再用取模算下标。& 0x7FFFFFFF 是为了把负数哈希转正。
性能差异
HashMap 用位运算 (n-1) & hash,要求容量为 2 的幂;Hashtable 用 %,容量可以是任意数。位运算比取模快一个数量级。
2. 初始容量与扩容¶
| HashMap | Hashtable | |
|---|---|---|
| 默认初始容量 | 16 | 11 |
| 负载因子 | 0.75 | 0.75 |
| 扩容 | newCap = oldCap << 1(2 倍) |
(oldCap << 1) + 1(2 倍 + 1) |
| 容量是否 2 的幂 | ✅ | ❌ |
Hashtable 扩容后是 2n+1,是为了配合取模时散列更均匀(与素数容量思想一致)。
3. 锁¶
// Hashtable 几乎所有 public 方法都 synchronized
public synchronized V put(K key, V value) { ... }
public synchronized V get(Object key) { ... }
// HashMap 完全无锁
public V put(K key, V value) { ... }
public V get(Object key) { ... }
Hashtable 的锁粒度是整个 Hashtable 对象,多线程下 get/put 互相阻塞。
4. null key / value¶
Hashtable 直接抛 NPE¶
HashMap 允许¶
static final int hash(Object key) {
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// null key 固定放在下标 0
5. 迭代器¶
- Hashtable 的迭代器是
Enumerator,支持hasMoreElements。 - HashMap 用
Iterator,fail-fast。 - Hashtable 的 enumerator 不是 fail-fast(不检查 modCount)。
一句话总结
HashMap 是 Hashtable 的优化替代品:更快的哈希、更合理的容量、允许 null、非线程安全;并发场景用 ConcurrentHashMap 而非 Hashtable。