Java:关于哈希表

Java 哈希表详解(以 HashMap 为核心)

在 Java 中,“哈希表”最典型、最常用的实现就是 HashMap(JDK 1.8+ 版本)。它几乎是所有 Java 开发者日常开发中使用频率最高的数据结构之一,也是面试中出现频率极高的考点。

下面从基础 → 原理 → 源码关键点 → 常见面试题 完整梳理。

1. 什么是哈希表(Hash Table / 散列表)?

哈希表是一种根据键(key)直接进行访问的数据结构。

核心思想:通过哈希函数把任意的 key 映射成一个固定范围的整数(数组下标),然后把键值对存储在数组对应位置。

理想情况下,查找/插入/删除的时间复杂度是 O(1)

但现实中必然存在哈希冲突(不同 key 算出相同下标),所以需要冲突解决策略

Java 中的 HashMap 采用的经典方案是:

  • 链地址法(Separate Chaining) → 数组 + 链表
  • JDK 1.8 优化 → 当链表过长时 → 转为红黑树

2. HashMap 的核心数据结构(JDK 1.8+)

HashMap 内部结构 = 数组(Node[] table) + 链表 + 红黑树(可选)
  • table:Node[] 数组,也叫哈希桶(bucket array)
  • 每个桶里可能是一个 Node(单节点),也可能是链表,也可能是红黑树
  • Node:静态内部类,包含 hash、key、value、next 指针

关键字段(默认值):

参数默认值含义
DEFAULT_INITIAL_CAPACITY16初始容量(必须是 2 的幂)
DEFAULT_LOAD_FACTOR0.75f负载因子(扩容阈值 = capacity × loadFactor)
TREEIFY_THRESHOLD8链表转红黑树阈值
UNTREEIFY_THRESHOLD6红黑树退化回链表阈值
MIN_TREEIFY_CAPACITY64转为红黑树的最小容量要求(防止过早转树)

3. HashMap 的核心流程(put / get)

put(K key, V value) 流程

  1. 计算 key 的 hash 值
   static final int hash(Object key) {
       int h;
       return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  // 高16位参与扰动
   }
  1. 计算数组下标(最关键的一步)
   index = (n - 1) & hash    // n = table.length,必须是2的幂
  1. 插入逻辑:
  • 如果该位置为空 → 直接放入新 Node
  • 如果已有元素:
    • hash 相同且 key equals → 覆盖 value
    • 否则 → 加入链表尾部(JDK 1.8 尾插法)
  • 如果链表长度 ≥ 8 且 table 容量 ≥ 64 → 转为红黑树
  • 插入后 size++,如果 size > threshold → 扩容

get(K key) 流程

  1. 计算 hash 和 index
  2. 在 table[index] 位置查找:
  • 如果是普通 Node → 直接比较
  • 如果是链表 → 顺序遍历比较 key
  • 如果是红黑树 → O(log n) 查找

4. 为什么 JDK 1.8 要引入红黑树?

JDK 1.7:只有数组 + 链表(头插法,扩容时可能死循环)

JDK 1.8 变化:

  • 尾插法(避免扩容死循环)
  • 链表长度 ≥ 8 + 容量 ≥ 64 → 转为红黑树
  • 红黑树节点数 ≤ 6 → 退化为链表

为什么是 8?
(官方 + 统计学依据)

  • 泊松分布下,链表长度达到 8 的概率非常低(约百万分之一)
  • 当冲突严重到链表长度 8 时,说明 hash 分布已经很差了
  • 转为红黑树后,查询从 O(n) → O(log n),收益明显
  • 小于 8 时,链表遍历更快(缓存友好、节点少)

为什么退化阈值是 6?
留出缓冲区间(8 → 7 → 6),避免频繁树化和退化切换(抖动)。

5. 哈希冲突的几种解决方式对比(面试常问)

方式原理优点缺点Java HashMap 是否使用
开放地址法冲突时找下一个空位无额外空间容易聚集,删除复杂
链地址法冲突时挂链表简单,实现容易链表过长查询慢是(主方案)
再哈希法多个 hash 函数冲突率低计算开销大
红黑树优化链表长时转平衡树查询 O(log n)维护复杂,节点开销大是(JDK 1.8+)

6. HashMap 经典面试题速查(2025-2026 高频)

题号问题核心答案要点
1HashMap 底层数据结构?数组 + 链表 + 红黑树(JDK 1.8+)
2hash() 方法为什么要有扰动?让高位也参与运算,减少冲突(低位相同,高位不同也能分散)
3为什么容量必须是 2 的幂?(n-1) & hash 运算等价于取模,且位运算更快
4负载因子为什么是 0.75?时间与空间的折中(太小浪费空间,太大链表长性能差)
5put 时扩容机制?size > threshold 时扩容为原来的 2 倍,rehash(JDK 1.8 优化为高低位分离)
6HashMap 为什么线程不安全?put/resize 时多线程竞争会导致数据丢失、死循环(1.7)、覆盖等
7为什么链表转红黑树阈值是 8?泊松分布 + 性能权衡(链表遍历快 vs 树查找快)
8HashMap 可以放 null 键/值吗?可以,null key 的 hash=0,放 table[0]
9HashMap 和 Hashtable 的区别?Hashtable 线程安全(synchronized 方法)、不允许 null、不建议使用
10ConcurrentHashMap 和 HashMap 区别?线程安全,1.8 后用 CAS + synchronized,细粒度锁

总结一句话

HashMap 是“数组 + 链表 + 红黑树”的哈希表实现,通过扰动函数 + 链地址法 + 红黑树优化,追求 O(1) 平均时间复杂度,同时在极端冲突下也能退化到 O(log n)。

你现在最想深入哪一块?

  • 扩容时高低位分离的具体代码过程
  • 红黑树节点插入/删除的简化讲解
  • HashMap 在多线程下的具体不安全表现
  • 手写一个简易 HashMap

告诉我,我可以继续展开更细!

文章已创建 4391

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部