手写 JDK 1.7 的 HashMap 实现
前言
这里将参考 JDK 1.7 的 HashMap 底层源码,模拟手写一个简易版的 HashMap。
思路
JDK 1.7 是如何处理哈希冲突的?
在 JDK 1.7 中,HashMap 在处理哈希冲突时采用的是链地址法(Separate Chaining)。当发生哈希冲突时,即多个键被映射到了同一个哈希桶(数组的位置),HashMap 会将这些键值对存储在同一个哈希桶对应的链表中。具体来说,在 JDK 1.7 中,HashMap 的每个哈希桶(数组的位置)实际上是一个链表,每个链表存储了哈希值相同的键值对。当执行 Put 操作时,HashMap 首先会计算键的哈希值,然后确定该键应该存储在数组的哪个位置。如果该位置已经存在了链表,HashMap 就会遍历该链表,检查是否已经存在相同键的键值对。如果存在相同的键,则 HashMap 会更新相应的值;如果不存在相同的键,则 HashMap 会将新的键值对添加到链表的末尾。链地址法的优点是它能够处理哈希冲突,并且在一定程度上保持了 HashMap 的性能。然而,在负载因子较高的情况下,即链表较长的情况下,查询键值对的效率可能会降低,因为需要遍历链表来找到目标键值对。
JDK 1.8+ 是如何处理哈希冲突的?
在 JDK 1.8 之后,HashMap 的实现发生了变化。JDK 1.8 引入了红黑树来替代链表,以改善在负载因子较高时的性能,这种结构称为 “链表与红黑树混合实现”。具体来说,当哈希冲突发生时,如果链表的长度超过一定阈值(默认为 8),HashMap 会将链表转换为红黑树。这样做的目的是为了在链表长度较长时提高查询、修改和删除操作的效率,因为红黑树的时间复杂度更稳定,为 O (log n)。而当链表长度较短时,仍然保持使用链表结构,因为在较短的链表中,链表的遍历效率更高。值得一提的是,当红黑树中元素个数小于一定数量时,会转换回原来的链表结构,JDK 设置这个默认数量为 6 个。
JDK 1.8+ 中 HashMap 的数据结构图
提示
从 JDK 8u40 版本开始,引入了 "树化优化"(Tree bins optimization),也就是 JDK 的底层是基于 数组 + 链表 + 红黑树 + 树化优化
来实现 HashMap。这个优化主要是在进行树化操作时,会先判断当前链表长度是否大于等于 8,如果不是,则不会进行树化操作,以节省资源。这个优化主要是为了解决在一些场景下,链表长度虽然超过了阈值,但树化操作并不能带来性能提升的问题。在一些更新的 JDK 版本中,可能还会引入其他优化措施,比如移位操作的优化,以进一步提升 HashMap 的性能。
代码
- 定义接口
1 | public interface MyMap<K, V> { |
- 基于数组 + 单向链表实现 HashMap
1 | public class MyHashMap<K, V> implements MyMap<K, V> { |
测试一
1 | public class MyHashMapTest1 { |
输出结果:
1 | 1 |
测试二
1 | public class MyHashMapTest2 { |
输出结果:
1 | 耗时:13ms |