← 返回题目列表

HashMap、HashSet 和 Hashtable 有什么区别?

高频 简单 第 4 / 29 题 更新于 2026/07/28
HashMapHashSetHashtable线程安全

简化版

三者底层都用哈希表。HashMap 存「键值对」,线程不安全、允许一个 null 键和 null 值,是最常用的;HashSet 存「不重复的元素」,底层其实就是一个只用键的 HashMapHashtable 是远古遗留的键值对实现,方法都加了 synchronized(线程安全但慢)、不允许 null,现在基本被 ConcurrentHashMap 取代。

详细版

维度HashMapHashSetHashtable
存什么键值对 key-value单个元素(去重)键值对 key-value
底层数组+链表+红黑树内部持有一个 HashMap数组+链表
线程安全是(方法 synchronized
允许 null1 个 null 键、多个 null 值允许 1 个 null 元素键值都不允许 null
性能慢(锁整表)
出身Java 1.2,Map 接口Java 1.2,Set 接口Java 1.0,遗留类

核心记忆HashSet = 只关心 key 的 HashMapHashtable = 加了锁的老古董,别用它,要线程安全用 ConcurrentHashMap

完整版教学

一、HashMap:主角

HashMap<K,V> 存键值对,底层是「数组 + 链表 + 红黑树」(Java 8+)。特点:

  • 线程不安全:并发 put 可能丢数据、扩容可能出问题,多线程别直接用。
  • 允许 null:可以有 1 个 null 键(放在下标 0 的桶),value 可以有多个 null。
  • 遍历无序(顺序和插入无关,扩容后还会变)。

它是日常开发用得最多的 Map 实现。

二、HashSet:披着 Set 外衣的 HashMap

HashSet 内部就藏了一个 HashMap:你 add(e) 的元素被当作 HashMapkey,value 统一填一个固定的空对象 PRESENT

// HashSet 内部
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT) == null;   // 复用 HashMap 的 key 去重
}

所以 HashSet 的「去重」本质就是 HashMap 的「key 唯一」。它天然继承了 HashMap 的一切特性:无序、线程不安全、允许一个 null 元素。判断重复靠 hashCode() + equals()

三、Hashtable:不该再用的遗留类

Hashtable 是 Java 1.0 的老类,比集合框架还早。问题:

  • 每个方法都 synchronized:锁的是整张表,并发时所有读写互相阻塞,性能很差。
  • 不允许 null 键或 null 值:put null 直接抛 NullPointerException
  • 设计陈旧,已被官方标注为「若需线程安全,请用 ConcurrentHashMap」。

ConcurrentHashMap 用分段锁(Java 7)/ CAS + synchronized 桶级锁(Java 8)实现细粒度并发,比 Hashtable 锁整表高效得多。

四、为什么 HashMap 允许 null 而 Hashtable 不允许

Hashtable 认为 null 有歧义:get(key) 返回 null 时,分不清是「key 不存在」还是「值本身是 null」,且它的哈希直接调 key.hashCode(),null 会 NPE。HashMap 特殊处理了 null 键(固定放 0 号桶),并提供 containsKey 来区分「不存在」和「值为 null」,所以能容忍 null。

五、面试高频追问

  • 要线程安全的 Map 用什么?ConcurrentHashMap(不是 Hashtable,也不是 Collections.synchronizedMap,后者也是锁整表)。
  • HashSet 怎么保证不重复? → 靠元素的 hashCode() 定位桶 + equals() 比较,两者都要正确重写。
  • HashMap 和 HashSet 谁包含谁? → HashSet 内部用 HashMap 实现。

六、常见误区与追问

结构底层关系线程安全null 支持
HashMapkey-value 哈希表非线程安全允许一个 null key,多个 null value
HashSet基于 HashMap 的 key非线程安全可放一个 null 元素
Hashtable老式同步哈希表方法级同步不允许 null key/value

易错点:HashSet 不是另一套神秘结构,它主要是把元素当作 HashMap 的 key,value 使用一个占位对象。

数字例子:向 HashSet 放入 1000 个元素,本质上类似向内部 HashMap 放入 1000 个 key,每个 key 对应同一个 dummy value。判断元素是否存在,就是看内部 map 是否包含这个 key。因此 HashSet 的去重语义依赖元素的 hashCodeequals 是否正确。

  • 误区:HashSet 比 HashMap 更底层。 HashSet 通常是基于 HashMap 封装出来的集合视图。
  • 误区:Hashtable 线程安全所以现在更推荐。 它是遗留类,方法级同步粒度粗;并发场景通常考虑 ConcurrentHashMap。
  • 误区:HashMap 的 key 相同只看 hashCode。 hashCode 定位桶,equals 确认逻辑相等,两者都重要。
  • 追问:HashSet 如何保证不重复? 新元素作为 key 放入 HashMap,key 已存在时覆盖占位 value,不增加新元素。
  • 追问:为什么重写 equals 要重写 hashCode? 相等对象必须有相同 hashCode,否则可能落到不同桶,集合语义会错。
  • 追问:HashMap 能否在多线程下直接写? 不能保证线程安全,可能出现数据丢失、可见性问题或结构异常。

七、加强记忆

HashMap 存键值对、线程不安全、允许 null、最常用;HashSet 就是「只用 key 的 HashMap」,靠 key 唯一去重;Hashtable 是加了 synchronized、不许 null 的遗留类,性能差,要线程安全请改用 ConcurrentHashMap。三者底层都是哈希表。