跳转至

从源码角度说明 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

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

直接用 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

if (value == null) {
    throw new NullPointerException();
}
// key 也不允许 null,因为要用 key.hashCode()

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。