HashMap常见面试题

1.HashMap 的数据结构?

jdk7 数组+单链表
jdk8 数组+单链表+红黑树

2.传统HashMap的缺点(为什么引入红黑树?)

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。

在jdk1.8版本后,java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。
红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。加快检索速率。

3.HashMap 是如何扩容的?

当容器中的元素个数大于 capacity * loadfactor 时,容器会进行扩容resize 为 2n
ps:
容量(Capacity):是指 HashMap 中桶的数量,默认的初始值为 16。
负载因子(LoadFactor)):也被称为装载因子,默认值为 0.75f

4.HashMap为什么二倍扩容?

HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低!

5.HashMap与ConcurrentHashMap扩容时,有啥区别?

HashMap 是直接在老数据上面进行扩容,多线程环境下,会有线程安全的问题,而 ConcurrentHashMap 就不太一样,扩容过程是这样的:
从数组的队尾开始拷贝;
拷贝数组的槽点时,先把原数组槽点锁住,拷贝成功到新数组时,把原数组槽点赋值为转移节点;
从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组的槽点设置成转移节点;
直到所有数组数据都拷贝到新数组时,直接把新数组整个赋值给数组容器,拷贝完成。
简单来说,通过扩容时给槽点加锁,和发现槽点正在扩容就等待的策略,保证了 ConcurrentHashMap 可以慢慢一个一个槽点的转移,保证了扩容时的线程安全,转移节点比较重要

6.HashMap中链表何时转化为红黑树?

在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)

7.HashMap插入数据方式?

JDK 7 使用的是头插法(先将原位置的数据移到后 1 位,再插入数据到该位置)
JDK 8 使用的是尾插法(直接插入到链表尾部/红黑树)

8.为什么提倡数组大小是 2 的幂次方?

因为只有大小是 2 的幂次方时,才能使 hash 值 % n(数组大小) == (n-1) & hash 公式成立。

<think>好的,我现在需要处理用户关于Java HashMap的面试题及答案的查询。首先,用户提供了几个引用资料,我需要仔细阅读这些资料,提取关键信息,然后组织成结构清晰的回答。 首先,引用[1]提到HashMapJava中重要且用的数据结构,面试中被问到,并列举了一些经典问题,比如工作原理。引用[2]和[3]都强调了HashMap在面试中的高频出现,通会出现在前几题,考察候选人对JDK源码的理解。引用[4]详细列出了多个面试题,包括数据结构、哈希冲突处理、扩容机制等。引用[5]则给出了回答HashMap底层实现时的要点,如初始容量、负载因子、红黑树转换等。 接下来,我需要根据这些信息整理出见的面试题及答案。要注意用户提到的系统级指令,比如行内数学公式用$...$,独立公式用$$...$$,但这里可能用不到数学公式。不过需要确保回答结构清晰,分点列出问题和答案,并正确添加引用标识。 可能的面试题包括: 1. HashMap的工作原理? 2. 如何处理哈希冲突? 3. 扩容机制是怎样的? 4. 为什么使用红黑树? 5. HashMap和HashTable的区别? 6. 初始容量和负载因子的作用? 7. 哈希函数如何实现? 8. 线程安全问题? 9. 与ConcurrentHashMap的区别? 10. 使用场景? 对于每个问题,需要结合引用中的内容给出简明扼要的回答,并在末尾添加引用标记。例如,工作原理涉及数组+链表+红黑树的结构,哈希冲突通过链表和红黑树解决,扩容机制涉及重新计算索引等。 需要确保答案准确,引用正确的来源,比如引用[4]提到jdk7和jdk8的结构变化,引用[5]提到初始容量和负载因子。同时,注意回答要符合中文表达,避免技术术语使用错误。 最后,生成相关问题,可能包括优化方法、性能分析、具体源码实现等,确保问题与主题相关,帮助用户进一步学习。</think>### Java HashMap 见面试题及答案 #### 1. HashMap 的底层数据结构是什么? JDK 1.7 及之前使用**数组+链表**,JDK 1.8 引入**数组+链表+红黑树**。当链表长度超过阈值(默认8)时,链表转换为红黑树以提升查询效率[^4]。 #### 2. HashMap 如何处理哈希冲突? - 通过**链地址法**(链表)解决冲突,即哈希值相同的键值对存储在同一个数组位置的链表中。 - JDK 1.8 后,当链表长度超过阈值,链表转换为红黑树,将查询时间复杂度从$O(n)$降至$O(\log n)$。 #### 3. HashMap 的扩容机制是什么? - 默认初始容量为16,负载因子为0.75。当元素数量超过$容量 \times 负载因子$时,触发扩容: - 容量扩大为原来的2倍。 - 键值对重新计算索引并分配到新数组中[^5]。 #### 4. 为什么 JDK 1.8 引入红黑树? 链表过长会导致查询效率降低,红黑树通过平衡二叉树的特性,将最坏情况下的时间复杂度从$O(n)$优化为$O(\log n)$[^4]。 #### 5. HashMap 和 HashTable 的区别? | **HashMap** | **HashTable** | |---------------------------|----------------------------| | 线程不安全 | 线程安全(方法加 synchronized 锁) | | 允许键/值为 null | 不允许键/值为 null | | 默认初始容量16 | 默认初始容量11 | | JDK 1.2 引入 | JDK 1.0 引入 | [^4] #### 6. 初始容量和负载因子的作用? - **初始容量**:决定哈希数组的初始大小,减少扩容次数。 - **负载因子**:控制扩容阈值(容量 × 负载因子),权衡时间与空间效率(默认0.75)。 #### 7. 哈希函数如何实现? 键的哈希值通过`hashCode()`计算,再通过扰动函数(异或高16位与低16位)减少哈希冲突: ```java static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ``` 扰动函数使哈希分布更均匀[^4]。 #### 8. HashMap 为什么线程不安全? 多线程环境下,扩容时可能导致链表形成环形结构(JDK 1.7),或数据覆盖问题(JDK 1.8)。 #### 9. HashMap 和 ConcurrentHashMap 的区别? - **HashMap**:线程不安全,效率高。 - **ConcurrentHashMap**: - JDK 1.7 使用分段锁(Segment)。 - JDK 1.8 改用 CAS + synchronized 锁单个(Node),并发性能更高[^4]。 #### 10. HashMap 的应用场景? - 高频键值查询(如缓存)。 - 无需保证线程安全的场景。 - 需要快速插入、删除、查找(时间复杂度接近$O(1)$)。 --- §§ 1. HashMap 在多线程下如何避免死循环? 2. 如何设计一个自定义对象作为 HashMap 的键? 3. ConcurrentHashMap 的 size() 方法如何实现? 4. 为什么负载因子默认值是0.75? 5. HashMap 的键为 null 时存储在哪个位置? [^1]: 引用1 [^2]: 引用2 [^3]: 引用3 : 引用4 : 引用5
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值