在Java里HashMap和Hashtable有什么区别_JavaMap线程安全解析

Hashtable 自 JDK 1.2 起被弃用,应改用 ConcurrentHashMap;其全表锁导致并发性能差,不支持 null 键值,且哈希计算、扩容机制均落后于 HashMap。

别再用 Hashtable 了,它已经被淘汰

直接说结论:Hashtable 是 JDK 1.0 的遗留类,自 JDK 1.2 起就被标记为 @Deprecated(不推荐使用),官方文档明确建议用 HashMap + 同步机制,或更优的 ConcurrentHashMap 替代。你在新项目里写 new Hashtable(),CI/CD 流水线甚至可能报警告;IDE(如 IntelliJ)也会划波浪线提示“Use ConcurrentHashMap instead”。这不是风格偏好,而是事实性技术淘汰。

线程安全 ≠ 可用:为什么 Hashtablesynchronized 反而害人

Hashtable 所有方法(put()get()size() 等)都加了 synchronized,锁的是整个对象——也就是「全表锁」。这意味着:哪怕你只是并发读两个完全不相干的 key,也得排队等锁释放。

  • 高并发下吞吐量骤降,QPS 可能只有 ConcurrentHashMap 的 1/5~1/10
  • size() 这种只读操作也要锁,而 ConcurrentHashMap

    size() 是无锁估算,快得多
  • 无法实现复合操作的原子性(比如「若不存在则 put」),Hashtable 仍需额外同步块

真要线程安全,请用:

Map map = new ConcurrentHashMap<>();

不是 Collections.synchronizedMap(new HashMap())——它也是全表锁,和 Hashtable 性能几乎一样差。

null 键值支持:一个空指针异常就暴露选型错误

这是开发中最常踩的坑:Hashtablenull 零容忍,只要 put(null, "x")put("k", null),立刻抛 NullPointerException;而 HashMap 允许一个 null 键(存于数组索引 0)、任意多个 null 值。

  • Web 开发中常见场景:HTTP 参数未传时值为 null,用 Hashtable 直接崩
  • 配置中心、缓存层常用 null 表示「未设置」或「禁用」,Hashtable 无法表达这类语义
  • 调试时如果看到 NPE 报在 table.put(...),第一反应应是检查是否误用了 Hashtable

底层差异不只是“老 vs 新”:哈希计算、扩容、树化全都不一样

这些细节直接影响性能和稳定性,尤其在数据量大、热点 key 多的场景:

  • HashMap 容量强制为 2 的幂(默认 16),索引计算用位运算 hash & (capacity - 1),比取模快一个数量级;Hashtable 容量默认 11,索引用 hash % capacity,还要先做 hash & 0x7FFFFFFF 保证正数
  • HashMap(JDK 8+)链表长度 ≥ 8 且数组容量 ≥ 64 时自动转红黑树,最坏查询从 O(n) 降到 O(log n);Hashtable 始终只有链表,大量哈希冲突时性能断崖下跌
  • HashMaphashCode() 做扰动:(h = key.hashCode()) ^ (h >>> 16),减少低位碰撞;Hashtable 直接用原始 hashCode(),对低质量 hash 函数更敏感

如果你还在维护老系统里用 Hashtable 的代码,迁移成本其实很低:全局替换为 ConcurrentHashMap,删掉所有 contains()(它已废弃,改用 containsKey()containsValue()),再检查是否有地方依赖 Enumeration ——那部分也该换成 Iterator 了。