在Java里HashMap与Hashtable有什么区别_Java映射类对比说明

应避免使用 Hashtable,优先选择 ConcurrentHashMap(线程安全)或 HashMap(单线程高效);它因全表同步、不支持 null、非 fail-fast、API 过时等问题已被淘汰。

别用 Hashtable,除非你在维护 JDK 1.0 时代的遗留系统。 它已被明确淘汰,ConcurrentHashMapHashMap 覆盖了全部合理场景。

线程安全不是免费的:synchronized 方法拖垮性能

Hashtable 所有 public 方法(如 put()get()size())都加了 synchronized,相当于整张表一把大锁。哪怕两个线程操作完全不冲突的 key,也得排队——这是它在单线程/低并发下比 HashMap 慢 2–5 倍的主因。

  • 多线程写入时,Hashtable 会严重阻塞;ConcurrentHashMap 则分段加锁或使用 CAS,吞吐量高得多
  • HashMap 完全不处理线程安全,直接裸奔——在单线程或已由外部同步(如 synchronized 块包裹)的场景下,它最轻快
  • 想“临时线程安全”?Collections.synchronizedMap(new HashMap()) 可行,但仅保证单个方法原子性,迭代 + 修改 这类复合操作仍需手动同步

null 键值支持:一个报错,一个容忍

Hashtablenull 零容忍:put(null, "v")put("k", null) 立刻抛 NullPointerException;而 HashMap 允许一个 null 键(存放在桶数组索引 0 处)和任意多个 null 值。

  • 这是最常踩的坑:把 HashMap 替换为 Hashtable 时,只要代码里存在 map.put(key, value) 且任一变量可能为 null,运行期必崩
  • 反向替换(HashtableHashMap)通常安全,但要注意逻辑是否隐式依赖“null 不可存”的行为(比如用 get(k) == null 判断 key 不存在——在 HashMap 中这可能是 key 不存在,也可能是 key 存在但值为 null

迭代器行为差异:fail-fast 是调试利器

HashMapIterator 是 fail-fast 的:遍历时若其他线程或同一线程未通过迭代器修改结构(如 remove()),会立即抛 ConcurrentModificationExceptionHashtable

Enumeration(老式遍历)和 Iterator(因实现 Map 接口而支持)都不保证 fail-fast,可能返回脏数据或静默失败。

  • 这意味着:用 HashMap 时,迭代中误调 map.remove(k) 会立刻暴露问题;用 Hashtable 可能跑完才出错,或结果不可靠
  • 安全遍历删除必须用迭代器自身的 remove() 方法,例如:
    Iterator> it = map.entrySet().iterator();
    while (it.hasNext()) {
        Map.Entry e = it.next();
        if (e.getValue() < 0) it.remove(); // ✅ 正确
    }

继承与 API 设计:Dictionary 已成历史名词

Hashtable 继承自早已废弃的抽象类 Dictionary(JDK 1.0),而 HashMap 直接实现 Map 接口并继承 AbstractMap(JDK 1.2 引入)。这也导致部分 API 不一致:

  • Hashtablecontains(Object value) 方法(易误解为查 key),HashMap 已移除,只留 containsKey()containsValue()
  • Hashtable 初始容量是 11,扩容为 2 * old + 1HashMap 初始为 16(2 的幂),扩容为 old * 2,配合位运算优化 hash 定位
  • Hashtable 没有 computeIfAbsent()merge() 等 Java 8+ 函数式方法,强行用会触发编译错误

真正需要线程安全时,选 ConcurrentHashMap;单线程或可控并发,选 HashMapHashtable 唯一合理的存在理由,是对接某些强制要求其类型的老旧 API——这种情况下,务必确认 null 处理和迭代逻辑不会翻车。