Java社招面试题:Map的Key你真的了解吗?

软件求生 2025-01-04 22:20:57

 小伙伴们,今天我们来聊一个在社招面试中常见又经典的问题——HashMap的扩容操作是怎么实现的? 这可是每个 Java 开发者都绕不开的基础问题,也是面试官爱考察的重点!不管你是职场小白,还是开发老手,如果对这个问题含糊不清,可能会在面试中被追问得哑口无言。 从一次面试开始 那天,刚入职场的小林去参加一家知名互联网公司的面试。面试官面带微笑地问道:“小林,能不能简单说一下 HashMap 的扩容机制?” 小林想了想,说:“HashMap 扩容是为了防止哈希冲突过多。容量会翻倍,旧元素会重新分配到新的桶中。” “嗯,不错。”面试官点了点头,接着追问:“那你知道具体是怎么实现的吗?比如,什么时候触发扩容?扩容后为什么不会导致数据丢失?” 小林有点慌了,只能支支吾吾地说:“我只知道……它会重新计算位置……” 这时,面试官的笑容明显收敛了一些。 那么,HashMap 的扩容到底是怎么回事? 接下来,我们以Java 8 的 HashMap为例,一步步带大家深入这个问题。 HashMap 的基本构造 在了解扩容之前,先搞清楚 HashMap 的基本构造。它的核心部分包括以下几块: 数组(table):存储所有键值对的桶,初始大小是默认的 16。 链表或红黑树:用于解决哈希冲突。桶中的每个元素其实是一个链表的头节点,当链表长度超过一定阈值(8)时会转为红黑树。 负载因子(loadFactor):用来衡量装满程度的参数,默认是 0.75。 容量(capacity):桶数组的长度,必须是 2 的幂。 阈值(threshold):扩容的临界值,等于 capacity * loadFactor。 什么时候触发扩容? 当 HashMap 中的元素数量超过 阈值 时,就会触发扩容操作。例如: 初始容量是 16,默认负载因子是 0.75。 阈值 = 16 × 0.75 = 12。 当元素数量达到 13 时,就需要扩容。 扩容后,容量会变成当前容量的两倍,即 32。 扩容的步骤是怎样的? 扩容分以下几个关键步骤: 1. 创建新数组 HashMap 会创建一个新桶数组,新数组的大小是旧数组的两倍。比如从 16 扩容到 32。 2. 重新计算哈希值 旧数组中的每个键值对会被重新分配到新数组中,重新计算其存放位置。 新的索引是通过公式计算的:

这里的 newCapacity 就是新桶数组的大小。 3. 分割链表 Java 8 引入了优化,当扩容时,如果桶中是链表(而不是红黑树),它会把链表分为两部分: 第一部分依然存储在原索引位置。 第二部分存储在原索引 + oldCapacity 的位置。 扩容的源码解读 我们来看一下关键代码:

重点是 transfer 方法,它完成了重新分配数据的过程:

为什么不会丢数据? 因为在扩容过程中: 每个节点都会被重新计算索引。 数据转移后,链表的指针关系依然保持完整。 数据转移是单线程完成的,没有中断风险。 红黑树的特殊处理 如果链表已经转化为红黑树,扩容时会重新构造红黑树,以保证高效性。相比链表,红黑树的搜索效率更高,时间复杂度从 O(n) 降低到 O(log⁡n)。 总结 HashMap 的扩容是当元素数量超过阈值时触发的,默认会将容量翻倍。 扩容过程中,每个键值对会重新计算哈希值并转移到新数组中。 Java 8 对链表和红黑树进行了优化,使扩容效率更高。 再回到小林的面试 小林离开面试后,总结了自己的不足,重新梳理了 HashMap 的扩容机制。后来,他又去了一家大厂面试,当被问到同样的问题时,他从触发条件、实现步骤到源码细节,讲得头头是道。面试官满意地点头:“不错,你对底层理解得很深!” 于是,小林顺利拿到了 offer。 END 这就是今天的分享!大家对 HashMap 的扩容还有什么疑问吗?或者你在面试中遇到过哪些棘手的问题,欢迎在评论区留言,我们一起探讨~ 我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!

0 阅读:0
软件求生

软件求生

从事软件开发,分享“技术”、“运营”、“产品”等。