Hashtable是JDK1.0遗留类,线程安全但性能差、不支持null;HashMap是JDK1.2标准实现,非线程安全但高性能、支持null;高并发应选ConcurrentHashMap。

Java 中的 Hashtable 和 HashMap 都是键值对存储结构,但它们不是“可互换的替代品”,而是代表了 Java 集合框架不同阶段的设计思想。理解它们的演进脉络和关键差异,能帮你避开线程安全陷阱、null 异常和性能瓶颈。
历史定位:从遗留类到标准实现
Hashtable 是 JDK 1.0 就存在的类,继承自已废弃的 Dictionary 抽象类,属于早期 Java 的“原始集合”。它没有融入现代 Collections Framework(JCF),命名风格和 API 设计都带有年代感。
HashMap 是 JDK 1.2 引入的,作为 Map 接口的标准实现之一,继承自 AbstractMap,与 ArrayList、LinkedList 等统一在 JCF 体系下。它的出现,本身就是对 Hashtable 设计局限性的修正。
这意味着:
– 新项目中不应主动选择 Hashtable;
– 维护老系统时遇到 Hashtable,优先考虑迁移至 HashMap 或 ConcurrentHashMap;
– Properties 类虽继承自 Hashtable,但那是特例(专用于字符串键值配置),不改变 Hashtable 整体的淘汰定位。
线程安全机制:全局锁 vs 无锁 vs 细粒度锁
这是最常被误用的一点。很多人以为“Hashtable 安全,所以更稳妥”,却忽略了代价。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
-
Hashtable对所有 public 方法(put、get、size、containsKey等)加synchronized,锁的是整个对象实例——即同一时刻只能有一个线程执行任意操作,吞吐量随并发线程数增长而断崖式下降。 -
HashMap完全不加锁,单线程下无任何同步开销,性能最优;但多线程写入可能引发数据丢失、链表成环(JDK 1.7)、或ConcurrentModificationException。 - 若真需要线程安全,不要用
Collections.synchronizedMap(new HashMap())(仍是全局锁,性能≈Hashtable),而应直接选用ConcurrentHashMap——JDK 1.8 后采用 CAS + 桶级synchronized,读操作无锁,写操作只锁冲突桶,高并发下性能远超Hashtable。
null 支持与日常踩坑点
这个差异看似小,实则极易引发 NullPointerException,尤其在处理外部输入或可选字段时。
-
HashMap允许一个null键(哈希值固定为 0,存于数组索引 0 处)和任意数量的null值。代码中可自然写:map.put(null, "fallback"); -
Hashtable对null零容忍:只要key == null或value == null,立刻抛NullPointerException。这不是健壮性设计,而是早期为规避同步逻辑中空值歧义采取的保守策略。 - 常见错误:把
HashMap替换为Hashtable后,原逻辑中存在map.put(key, obj.getValue()),而obj.getValue()可能为null——运行时直接崩溃。
底层实现与性能表现
两者虽同为哈希表,但细节决定效率:
-
初始容量与扩容:
Hashtable默认容量 11,扩容公式为2 * old + 1(如 11→23→47),结果不是 2 的幂次,索引计算需取模(hash % capacity),较慢;HashMap默认容量 16(2 的幂),扩容翻倍(16→32→64),索引用位运算(n-1) & hash,更快更均匀。 -
哈希扰动:
HashMap(JDK 1.8+)对key.hashCode()做扰动处理h ^ (h >>> 16),降低低位相同导致的哈希碰撞;Hashtable直接使用原始hashCode,分布质量较差。 -
结构升级:
HashMap在链表长度 ≥ 8 且数组长度 ≥ 64 时自动树化为红黑树,最坏查询复杂度从 O(n) 降至 O(log n);Hashtable始终只有链表,大数据量下性能退化明显。


















