【面试题】HashMap如何解决hash碰撞的问题?

HashMap在遇到哈希碰撞时,采用链表存储冲突的键值对,当链表过长(超过8个节点)且数组容量足够时,会转换为红黑树以降低查询复杂度至O(logn)。转为红黑树的阈值基于概率学的泊松分布。在查找值时,通过寻址算法定位到数组下标,再遍历链表或红黑树寻找目标键值。

hash冲突问题解决方案:链表O(n)+红黑树O(logn)

正常一个位置放一对key-value,冲突后存放两对或多对key-value

[<>]数组中这个位置会挂一个链表。

上面为本问题最简单的回答。

继续问:这种挂链表的方式假设链表很长,会导致便利链表性能较差,达到时间复杂度O(n)

做了个优化:

如果链表长度达到一定长度后,链表会转化为红黑树。使用红黑树的好处是,当遍历红黑树的时候,时间复杂度变为O(logn),性能较高

要答出两点:

(1)出现hash冲突的时候,会在这个位置上挂一个链表,在遍历时,时间复杂度是O(n)

(2)当链表长度到达一定长度后,会将链表转化为红黑树,时间复杂度O(logn),性能更高

 

1.链表在多长的时候会转红黑树,为啥在这个长度转红黑树?

当链表长度超过8,并且经过扩容后当前数组长度大于64,会将链表转化为红黑树

而当HashMap的红黑树的元素小于等于6时重新转化为链表结构

为什么会在8转为红黑树,可以看一下代码的注释,注释上说了作者是根据概率学的角度来决定的,因为根据统计,一个桶位置上的节点数目的分布式泊松分布,长度超过8的概率十分小,所以作者选用了8作为链表转为红黑树的阈值

2.为什么JDK1.8 HashMap选择红黑树而不是其他的树?

是因

### 关于Java中HashMap的常见面试题及解答 #### 1. **什么是HashMap?其底层数据结构是什么?** HashMap 是基于哈希表的数据结构,允许存储键值对。它的核心工作原理是通过 `hashCode()` 方法计算键的哈希码,并将其映射到桶(bucket)的位置[^2]。 - 底层数据结构由数组和链表/红黑树组成,在 Java 8 中进行了优化,当某个桶中的节点数超过一定阈值(默认为 8),该桶会从链表转换为红黑树以提高查询效率。 #### 2. **为什么要在HashMap中重写`equals()`和`hashCode()`方法?** 为了确保自定义对象作为键时能够正常工作。如果两个对象相等,则它们的哈希码应该相同;反之则不一定成立。因此,`hashCode()` 和 `equals()` 的一致性对于 HashMap 正确运行至关重要[^1]。 以下是示例代码展示如何正确实现这两个方法: ```java class Student { private String id; private String name; @Override public int hashCode() { return Objects.hash(id, name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student student = (Student) obj; return Objects.equals(id, student.id) && Objects.equals(name, student.name); } } ``` #### 3. **HashMap 如何处理冲突?** 当多个键被分配到同一个桶时会发生冲突。HashMap 使用拉链法解决冲突——即在同一桶中维护一个链表或红黑树来保存这些键值对。在 Java 8 及之后版本中,当链表长度达到指定阈值(默认为 8)时,链表会被替换为红黑树以提升性能[^2]。 #### 4. **HashMap 的初始容量和加载因子的作用是什么?** - **初始容量**:创建 HashMap 实例时设定的大小,默认为 16。 - **加载因子**:表示 HashMap 能够容纳多少比例的元素而不触发扩容操作,默认为 0.75。 实际容量等于 `initialCapacity * loadFactor`。一旦超出此范围,HashMap 将自动调整内部数组大小并重新分布所有条目。 模拟扩容过程如下所示: ```java public class HashMapResizeDemo { public static void main(String[] args) { // 初始容量4,负载因子0.75,阈值3 HashMap<String, Integer> map = new HashMap<>(4, 0.75f); map.put("A", 1); map.put("B", 2); map.put("C", 3); // 触发扩容 System.out.println("扩容后容量:" + map.size()); } } ``` #### 5. **遍历 HashMap 的方式有哪些?** 可以通过多种方式进行迭代访问: - KeySet 遍历 - EntrySet 遍历 - Values 遍历 具体实现可参见以下代码片段: ```java import java.util.HashMap; import java.util.Map; public class HashMapTraversalExample { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("Alice", 90); map.put("Bob", 85); // KeySet 遍历 for (String key : map.keySet()) { System.out.println(key + ": " + map.get(key)); } // EntrySet 遍历 for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + "->" + entry.getValue()); } // Values 遍历 for (Integer value : map.values()) { System.out.println(value); } } } ``` #### 6. **HashMapHashtable 的主要区别是什么?** | 特性 | HashMap | Hashtable | |-------------------|-------------------------------------------|------------------------------------| | 线程安全性 | 不安全 | 安全 | | Null 键和值支持 | 支持 | 不支持 | | 性能 | 更高效 | 较低 | 更多细节可见参考资料[^4]。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值