面试官:JDK1.8 HashMap扩容rehash算法是如何优化的?

简介: 本文跟大家聊一聊一个常见的面试题,那就是JDK1.8 HashMap扩容rehash算法是如何优化的?

大家好,我是三友~~

本文跟大家聊一聊一个常见的面试题,那就是JDK1.8 HashMap扩容rehash算法是如何优化的?

众所周知HashMap的底层其实是一个数组,既然是一个数组,必然长度是固定的,也就一定存在扩容的问题。在JDK1.7的时候,是将数组扩容为两倍,然后将HashMap中所有的key重新进行hash寻址算法然后再放入到扩容后的新的数组的新的位置。

但是从JDK1.8之后,对rehash进行了优化,减少了对key重新进行hash寻址算法的过程,具体怎么实现的,这就上源码。

我们都知道HashMap扩容是通过resize来实现的,所以我们看看resize方法的实现

final Node<K,V>[] resize() {
   
   
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
   
   
            if (oldCap >= MAXIMUM_CAPACITY) {
   
   
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //这个 newCap = oldCap << 1 就是扩容两倍的证据
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {
   
                  // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
   
   
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({
   
   "rawtypes","unchecked"})
        //重新构建了一个新的数组,容量是上面计算出来的newCap
        //就是原来的两倍大小
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        //拿到老的数组,然后遍历每个数组的位置,对每个节点重新进行rehash
        if (oldTab != null) {
   
   
            for (int j = 0; j < oldCap; ++j) {
   
   
                Node<K,V> e;
                //当遍历到这个数组的位置有节点的时候,进入重新rehash
                if ((e = oldTab[j]) != null) {
   
   
                    oldTab[j] = null;
                    if (e.next == null)
                    //这个的意思很简单,就是这个节点只有一个
                    //也就是没有形成链表或者红黑树的时候
                    //此时处理就是重新进行hash寻址算法,找到在新数组的位置放上
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                    //就是这个已经是红黑树了,此时会进入红黑树rehash的过程
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {
   
    // preserve order
                    //这个就是链表的rehash的过程
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
   
   
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
   
   
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
   
   
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
   
   
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
   
   
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

其实rehash就是遍历数组的每个位置,判断节点的状态,是单个或者链表或者红黑树。接下来就每种情况进行讨论。

1)单个节点

image.png

其实重新进行hash寻址算法,找到对应数组的下标,放上就行了

2)链表

image.png

仔细阅读源码会发现,就是将之前的链表rehash之后重新拆分为了两个链表,一个链表rehash之后还是在当前的位置index,另一个链表rehash之后的位置变成了index + oldCap,画个图理解一下

image.png

至于为什么可以分为两个链表,这里说明一下。就是hash寻址算法对一个数组下标的所有节点,扩容后进行重新计算的时候,会发现计算出来的位置要么是在原来的index,要么实在原来的index + oldCap的位置,这是hash寻址的一个特点,所以基于这一个既定的结论,就去判断一下每个节点重新hash寻址之后是原来的位置还是index + oldCap的位置就行了(如何判断,就是源码图的第一个红框框出来的),判断是在原来的位置然后一个新的链表,在index + oldCap的位置也形成一个新的链表,这样计算完之后只要把新的两个链表挂在新的数组的 index 和 index + oldCap就行了(如何挂的,就是源码图的第二个红框框出来的)。这样就避免了对每个节点重新进行hash寻址算法,重新放到hash表中的过程,大大提高了效率,这也就是JDK1.8的HashMap扩容rehash算法优化。

3)红黑树

贴上源码

image.png

其实原理跟链表的差不多,就是链表拆成两个链表,红黑树这个拆成两个红黑树,分别挂到新的数组的位置上,只不过最后加个判断,就是判断这个红黑树是需要变成链表还是继续是红黑树。

所以在JDK1.8的rehash算法优化就是对原来的链表或者红黑树进行拆分成两部分,然后分别挂在原来数组的位置和 数组的位置 + oldCap的位置,这样做就避免了大量的节点进行重新hash寻址算法和重新放到hash表的过程,大大增加了扩容效率。

本文完。

PS:如果觉得这篇文章对你有帮助,欢迎大家关注公众号三友的java日记、分享、点赞、在看,感谢支持。

往期热门文章推荐

如何去阅读源码,我总结了18条心法

如何写出漂亮代码,我总结了45个小技巧

三万字盘点Spring/Boot的那些常用扩展点

三万字盘点Spring 9大核心基础功能

万字+20张图剖析Spring启动时12个核心步骤

1.5万字+30张图盘点索引常见的11个知识点

两万字盘点那些被玩烂了的设计模式

搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

相关文章
|
3天前
|
存储 Java 开发者
面试官:小伙子知道synchronized的优化过程吗?我:嘚吧嘚吧嘚,面试官:出去!
面试官:小伙子知道synchronized的优化过程吗?我:嘚吧嘚吧嘚,面试官:出去!
19 1
|
15天前
|
SQL 分布式计算 监控
Sqoop数据迁移工具使用与优化技巧:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入解析Sqoop的使用、优化及面试策略。内容涵盖Sqoop基础,包括安装配置、命令行操作、与Hadoop生态集成和连接器配置。讨论数据迁移优化技巧,如数据切分、压缩编码、转换过滤及性能监控。此外,还涉及面试中对Sqoop与其他ETL工具的对比、实际项目挑战及未来发展趋势的讨论。通过代码示例展示了从MySQL到HDFS的数据迁移。本文旨在帮助读者在面试中展现Sqoop技术实力。
27 2
|
1月前
|
算法
经典控制算法——PID算法原理分析及优化
这篇文章介绍了PID控制算法,这是一种广泛应用的控制策略,具有简单、鲁棒性强的特点。PID通过比例、积分和微分三个部分调整控制量,以减少系统误差。文章提到了在大学智能汽车竞赛中的应用,并详细解释了PID的基本原理和数学表达式。接着,讨论了数字PID的实现,包括位置式、增量式和步进式,以及它们各自的优缺点。最后,文章介绍了PID的优化方法,如积分饱和处理和微分项优化,以及串级PID在电机控制中的应用。整个内容旨在帮助读者理解PID控制的原理和实际运用。
88 1
|
1月前
|
机器学习/深度学习 算法 Oracle
ICLR 2024:近似最优的最大损失函数量子优化算法
【2月更文挑战第27天】ICLR 2024:近似最优的最大损失函数量子优化算法
32 3
ICLR 2024:近似最优的最大损失函数量子优化算法
|
1月前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
58 1
|
18天前
|
负载均衡 算法 应用服务中间件
面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
字节跳动面试题:Nginx有哪些负载均衡算法?Nginx位于七层网络结构中的哪一层?
32 0
|
1天前
|
算法 索引
数据结构与算法-并查集多种实现以及优化步骤
数据结构与算法-并查集多种实现以及优化步骤
5 0
|
2天前
|
机器学习/深度学习 人工智能 算法
揭秘深度学习中的优化算法
【4月更文挑战第24天】 在深度学习的广阔天地中,优化算法扮演着至关重要的角色。本文将深入探讨几种主流的优化算法,包括梯度下降法、随机梯度下降法、Adam等,并分析它们的特点和适用场景。我们将通过理论分析和实例演示,揭示这些优化算法如何帮助模型更高效地学习参数,从而提高模型的性能。
|
11天前
|
算法
R语言使用随机技术差分进化算法优化的Nelson-Siegel-Svensson模型
R语言使用随机技术差分进化算法优化的Nelson-Siegel-Svensson模型
20 0
|
18天前
|
算法 数据处理 C语言
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)