Skip to content

说说 Java 中 HashMap 的原理? ​

  • 标签:Java、Java 基础

🏛 业务架构师 ​

HashMap 是业务代码里使用频率最高的容器,我作为架构师更关心的是什么时候用它才安全。原理上它是数组 + 链表 + 红黑树:元素按 hash 定位到桶,冲突时挂链表,链表长度到 8 且数组容量达 64 就树化为红黑树,把最坏查询从 O(n) 压到 O(logn);元素数超过容量 × 负载因子(0.75)就 2 倍扩容重新分布。

从业务落地角度我抓三点:

  • 线程安全红线:秒杀库存扣减、实时在线人数这类并发读写场景绝不能用 HashMap,1.7 并发扩容会成环形链表死循环,1.8 也会丢数据,必须换 ConcurrentHashMap;
  • 容量预估:能预估数据量就传初始容量(预期数 / 0.75 + 1),避免大促场景反复扩容造成接口毛刺;
  • key 的设计:可变对象做 key 是埋雷,自定义 key 必须同时重写 hashCode 和 equals。

一句话总结:理解 HashMap 原理的价值在于让团队知道它的业务边界——单线程、读多写少、需要按 key 快速存取,越界就要换容器,而不是出了线上事故再补课。

🏗 系统架构师 ​

我一般从"数据结构 + 哈希冲突处理 + 扩容"三层来讲 HashMap:底层是数组 + 链表 + 红黑树(JDK 1.8+),put 时先对 key 的 hashCode 做扰动(高 16 位异或低 16 位),再与 capacity-1 按位与定位桶下标;冲突时拉链,链表长度到 8 且数组容量 ≥ 64 时转红黑树,把最坏查询从 O(n) 压到 O(log n)。

工程视角的关注点:

  • 负载因子 0.75、容量始终 2 的幂,扩容时元素要么留原位、要么移动"原下标 + oldCap",避免了重新计算 hash,这是 JDK 设计里很值得学的性能细节;
  • 它非线程安全,多线程并发扩容在 1.7 里可能成环导致 CPU 打满,1.8 改成尾插解决了成环但仍会丢数据,所以并发场景我一定换 ConcurrentHashMap;
  • 设计缓存或路由表时我会预估容量,new HashMap<>(expected/0.75f + 1) 一次性到位,避免线上高频扩容带来的毛刺。

💻 全栈开发专家 ​

HashMap 底层是数组 + 链表 + 红黑树(JDK 8+)。put 时先对 key 的 hashCode 做扰动(h ^ (h >>> 16)),再按 (n - 1) & hash 定位桶;碰撞走链表,链表长度到 8 且数组长度 ≥ 64 就树化,节点删到 6 退回链表。元素超过 capacity * 0.75 就扩容一倍。

java
// 核心定位逻辑,等价于 hash % n,但位运算更快
int index = (n - 1) & (key.hashCode() ^ (key.hashCode() >>> 16));

工程上的坑我踩过两个:

  • 自定义对象做 key 必须同时重写 hashCode() 和 equals(),否则 get 不到;而且 key 要不可变,字段变了 hash 对不上,内存泄漏。
  • 多线程并发 put 在 JDK 8 虽然不会死循环了(改成尾插),但仍会丢数据,并发场景直接换 ConcurrentHashMap,别侥幸。

排查 HashMap 相关问题时,我一般先看是不是 key 的 hashCode 质量差(大量碰撞),用 IDEA 的 debugger 看桶分布就能确认。