应避免使用Hashtable,优先选择HashMap(单线程)或ConcurrentHashMap(多线程);Hashtable不支持null键值、全表同步锁导致性能差、无红黑树优化、取模计算慢且哈希分布不均、迭代器机制易引发隐蔽bug。

别用 Hashtable,除非你在维护 2000 年的遗留系统。 它是 JDK 1.0 的产物,已被明确淘汰;现代 Java 开发中,HashMap 是默认选择,线程安全场景应优先用 ConcurrentHashMap,而非 Hashtable。
为什么 put(null, "x") 在 Hashtable 中直接崩溃
这是最常踩的坑:只要 key 或 value 是 null,Hashtable.put() 就会立即抛 NullPointerException —— 它连判空都懒得封装,源码里就是裸写 if (key == null) throw new NullPointerException();。
-
HashMap允许一个nullkey(固定存到数组索引 0 的位置),也允许任意多个nullvalue -
Hashtable对null零容忍,哪怕你只传了一个nullvalue,也会炸 - 如果你从 JSON 或数据库读数据,字段可能为
null,用Hashtable就等于主动埋雷
多线程下用 Hashtable 真的安全吗
“线程安全”不等于“高并发可用”。Hashtable 所有方法(put、get、size)都被 synchronized 修饰,锁的是整个对象 —— 每次操作都得排队,吞吐量极低。
- 单线程下,
HashMap插入百万条数据通常比Hashtable快 2–3 倍 - 多线程下,
ConcurrentHashMap使用 CAS + 分段/桶级锁,读操作无锁,写冲突概率大幅降低 -
Collections.synchronizedMap(new HashMap())虽然线程安全,但仍是全表锁,性能和Hashtable接近,不推荐
扩容慢、查询慢,底层结构暴露了代差
HashMap(JDK 8+)在哈希冲突严重时会把链表升级为红黑树,查找从 O(n) 降到 O(log n);Hashtable 至今仍只有数组 + 链表,且扩容公式是 newCapacity = oldCapacity * 2 + 1,导致容量永远是奇数,哈希分布更不均。
立即学习“Java免费学习笔记(深入)”;
-
HashMap初始容量 16(2 的幂),索引计算用hash & (capacity - 1),位运算快且散列均匀 -
Hashtable初始容量 11,索引靠hash % capacity,取模慢,且奇数容量加剧哈希碰撞 - 当数据量超过几千、又存在大量哈希冲突时,
Hashtable的get()可能退化成遍历长链表,延迟明显升高
迭代器失效机制不同,藏着隐蔽 bug
HashMap 的 Iterator 是 fail-fast 的:只要在遍历时有其他线程或本线程修改了结构(如 put、remove),立刻抛 ConcurrentModificationException —— 这是帮你提前发现并发问题。
-
Hashtable支持Enumeration(老式遍历器),它不检查修改,遍历时删元素不会报错,但可能漏数据、重复读、甚至死循环 -
Hashtable的Iterator虽也 fail-fast,但因全表锁,实际很难触发该异常,掩盖了设计缺陷 - 如果你在 for-each 循环里调
remove(),HashMap明确报错;Hashtable可能静默出错,调试成本更高
真正需要关心的不是“怎么选”,而是“为什么还看到 Hashtable 出现在代码里”——大概率是没更新的模板、过时的教程,或者某段没人敢动的老逻辑。它的存在本身,就是技术债的具象化。










