news 2026/11/6 3:14:05

Java中关于HashMap常见问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java中关于HashMap常见问题

HashMap数据结构

面试里问 HashMap,很多人张口就是“数组 + 链表 + 红黑树”。
但再追问一句:为什么容量必须是 2 的幂?为什么链表到 8 还不一定树化?扩容时元素到底怎么迁移?很多人就开始背八股了。

其实 HashMap 不用死记。你可以把它想成一个快递柜:

  • 数组就是一排柜格,负责快速定位;

  • hash决定包裹放哪个柜格;

  • 柜格里已经有包裹了,就叫哈希冲突,那就排队,形成链表;

  • 队伍太长,查询太慢,就升级成红黑树;

  • 柜子快装满了,就扩容,换一排更大的柜子重新分配。

下面我们跟着一次put,把 HashMap 的核心机制串起来。


一、一次 put 到底发生了什么?

当你写下:

map.put("张三", 18);

HashMap 大概做这几件事:

  1. 算出 key 的 hash;

  2. 根据 hash 和数组长度算出下标;

  3. 看这个位置是不是空的;

  4. 空就直接放;

  5. 不空就判断 key 是否相等:相等就覆盖 value,不相等就挂到链表或红黑树上;

  6. 元素太多就扩容。

一句话:

数组负责定位,链表/红黑树负责处理冲突,扩容负责控制负载。


二、下标是怎么算出来的?

HashMap 最终用这个公式算下标:

int index = (n - 1) & hash;

n是数组容量。

这里有两个关键点。

第一,容量必须是 2 的幂。

因为当n是 2 的幂时,n - 1的二进制低位全是 1。比如:

  • n = 16,n - 1 = 15,二进制是1111;

  • n = 32,n - 1 = 31,二进制是11111。

这样(n - 1) & hash就能均匀保留 hash 的低位,效果类似取模,但位运算更快。

如果容量不是 2 的幂,某些下标可能永远算不到,桶分布会不均匀。所以就算你手动指定了一个不是 2 的幂的初始容量,HashMap 也会帮你调整成最接近的、更大的 2 的幂。

第二,hash 会先扰动一下。

HashMap 不是直接用key.hashCode(),而是把 hash 的高位和低位混合一下,让结果更分散,减少冲突。你不用记具体代码,记住目的就行:让 hash 更散,少撞车。


三、冲突了怎么办?

算出下标后,如果那个位置是空的,直接放。

如果不空,就要处理冲突:

  • key 相等,覆盖 value;

  • key 不相等,挂到链表后面;

  • 链表太长,再考虑转红黑树。

这里判断 key 是否相等,不只看 hash,还要用equals()。所以自定义对象当 key 时,hashCode()和equals()要一起正确实现,不然会出现“找不到 key”或者“两个不同 key 被当成同一个”的问题。


四、链表什么时候变红黑树?

很多人只背“链表到 8 就变红黑树”,这是不完整的。

必须同时满足两个条件:

  1. 链表长度超过 8;

  2. 数组容量至少为 64。

如果链表到了 8,但数组容量还不到 64,HashMap 不会树化,而是先扩容。因为容量小的时候,扩容分散冲突更划算;树化本身也有额外开销。

反过来,红黑树节点减少到 6 时,会退回链表。为什么是 6 不是 8?因为留个缓冲,避免在 7、8 附近来回转换。

可以记成:

8 是树化线,64 是容量门槛,6 是退化线。

红黑树也不用深入理解,你只要知道:它是一种自平衡的树,查起来比长链表快。链表查询要一个个找,红黑树能少走很多步。


五、什么时候扩容?

默认情况下:

  • 初始容量是 16;

  • 负载因子是 0.75;

  • 扩容阈值是16 × 0.75 = 12。

当元素数量超过 12,就触发扩容。新容量是原来的 2 倍。

扩容最核心的动作是:把旧数组里的元素搬到新数组。

JDK 1.8 用了一个巧办法。容量翻倍后,元素的新位置只有两种可能:

  • 还在原来的下标;

  • 或者变成“原下标 + 旧容量”。

不用每个元素都重新算一遍完整下标。如果旧桶里是红黑树,迁移时会先拆成两个链表,再按新位置分配,必要时重新树化。

扩容的优点是降低冲突、提高查询效率;缺点是要搬元素,元素多时比较耗时。所以如果能预估元素数量,初始化时给一个合适容量,可以减少扩容次数。


六、几个关键参数,一张表记住

参数默认值作用常见误区
初始容量16桶数组初始长度不是随便设,最终会调成 2 的幂
负载因子0.75扩容阈值 = 容量 × 负载因子不是越大越好
树化阈值8链表转红黑树条件之一还要容量 ≥ 64
反树化阈值6红黑树退回链表留缓冲,避免频繁转换
最小树化容量64容量不够先扩容小容量树化不划算

负载因子 0.75 是时间和空间的平衡:太小会频繁扩容、浪费空间;太大会冲突严重、查询变慢。


七、常见误区,面试别踩

误区一:链表到 8 一定变红黑树。
不对,还要看数组容量是否 ≥ 64。容量不够先扩容。

误区二:容量可以随便指定。
HashMap 最终会把容量调整成 2 的幂,因为下标计算依赖这个特性。

误区三:负载因子越大越好。
负载因子大,空间利用率高,但冲突概率也高,查询会变慢。

误区四:扩容只是把数组变大。
扩容还要搬元素、重新分配位置,成本不低。


八、最后总结

HashMap 的核心就三句话:

  1. 用数组快速定位:(n - 1) & hash算下标,容量必须是 2 的幂。

  2. 用链表/红黑树处理冲突:冲突少挂链表,冲突严重且容量够大才树化。

  3. 用扩容控制负载:超过阈值容量翻倍,元素按规则迁移。

所以 HashMap 平均能做到 O(1) 的查找、插入和删除,不是因为它神奇,而是因为它在数组定位、冲突处理和扩容之间做了一套平衡。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 3:13:52

41:数字统计

/*** 题目名称&#xff1a;数1的个数 <p>* 题目来源&#xff1a;http://noi.openjudge.cn/ch0105/41/ <p>* 程序功能&#xff1a;输出指定范围整数中数字2出现的次数** author 潘磊&#xff0c;just_panleijust.edu.cn* version 1.0*/import java.util.Scanner;pub…

作者头像 李华
网站建设 2026/11/6 3:10:56

彻底复活老 Flash:Ruffle 浏览器扩展部署与调优全攻略

彻底复活老 Flash&#xff1a;Ruffle 浏览器扩展部署与调优全攻略 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle Ruffle 是一个用 Rust 编写的 Flash 模拟器&#xff08;Flash Player emu…

作者头像 李华
网站建设 2026/11/6 3:10:00

Codex-X 提示词注入指南:6 步快速启用破甲提示词

Codex-X 提示词注入指南&#xff1a;6 步快速启用破甲提示词 【免费下载链接】Codex-X OpenAI Codex 桌面端/CLI 的可视化管理工具&#xff0c;具有Provider/API 切换、会话同步、提示词注入、Skills/MCP 管理、TOML 配置可视化的跨平台工具。 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/11/6 3:09:58

Scrapling Agent Skill 完整指南:三步接入不猜 API 的官方技能包

Scrapling Agent Skill 完整指南&#xff1a;三步接入不猜 API 的官方技能包 【免费下载链接】Scrapling &#x1f577;️ The BEST Web Scraping framework that handles everything from a single request to a full-scale crawl! Dont be shy, join here: https://discord.g…

作者头像 李华