热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

HashMap的扩容知识详解

本文详细介绍了HashMap的扩容知识,包括扩容的概述、扩容条件以及1.7版本中的扩容方法。通过学习本文,读者可以全面了解HashMap的扩容机制,提升对HashMap的理解和应用能力。

文章概述:

 大家好,之前我们讲解了map的哈希冲突,相信各位已经迫不及待来学习咱们的map中篇了,我们中篇讲解的就是map的扩容知识,接下来我们就正式进入到学习中来,相信大家读完这篇文章后,一定能收获相关的知识。

一、扩容的概述

啥是扩容,现象是什么

 当 map中的元素超过了阈值,也就map中的规定的容量 ,此时就需要进行扩容,你想呀,如果map的数组都装满了,那么来了元素之后,不就只能都冲突了吗,所以我们在map满足了一定容量后,就得扩容啦~~那么怎么扩容呢~,并且扩容的条件是什么呢?

二、map1.7何时扩容

我们先来看看1.7的构造方法

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;                
    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
}

解释说明

DEFAULTINITIALCAPACITY= 16 默认的数组长度
DEFAULTLOADFACTOR = 0.75f 默认的负载因子
threshold:阈值的临界值

我们再来找找它扩容条件

if (size++ >= threshold)
    resize(2 * table.length);

 也就是说,在默认情况下:当map中的容量个数大于等于阈值时,也就是我们之前计算出来的 数组长度0.75就会扩容,比如初始情况下,扩容的条件就是16 * 0.75=12

注意:当我们向map中存放了一个元素,比如map.put(key,value),只要这个元素存入到了map中,我们就认为容量加1


三、 map1.7如何扩容

 其实map1.7的做法非常的简单,它会遍历当前这个数组,拿到数组中的每一个entry,然后再遍历entry上的每一个节点,然后把每一个节点重新计算其在新数组上的位置

 我们为了能更好的理解扩容,先来定两个假设,假设map的计算索引的方法是 用key % table.size() ,再假设map的数组长度是2,于是当我插入key 分别是 3,7,5 这三个数据时,它们计算出来的索引都是1号索引

 具体计算方式:3 % 2= 1 ; 7 % 2= 1 ; 5% 2 = 1;如下图:


源码如下:并标明基本的含义

// 参数说明:扩容后的map对象,长度为原来两倍
void transfer(Entry[] newTable) {
    // 原始map对象,未扩容前的
    Entry[] src = table;
    // 拿到新扩容后的长度
    int newCapacity = newTable.length;
    // 遍历数组
    for (int j = 0; j < src.length; j++) {
        //拿到每一个entry
        Entrye = src[j];  //标记0 
        if (e != null) {        //标记1
            //断开和原来map之间的关系
            src[j] = null;      //标记2
            do {
                // 具体分析见后文
                Entrynext = e.next;  //标记3
                int i = indexFor(e.hash, newCapacity); //标记4
                e.next = newTable[i];//标记5
                newTable[i] = e; //标记6 
                e = next; //标记7
            } while (e != null); //标记8    
        }
    }
}



以下内容建议大家拿着源码对照着看哦

key为3 的节点转移情况说明:

 在标记0 处,Entry e = src[1] = 0x01 ,此时满足标记1的情况,同时在标记2 处,src[1]=null ,key=3的entry节点与原map断开关系,此时再标记3 处Entrynext = e.next 取出来具体的值 就变成了Entry next = 0x02,

而重新去计算索引值的标记4 ,按照咱们的约定,我们算出来是3 % 4= 3; 接着再标记5处,newTable[3] 的值也就是一个null (注意:new出来的对象数组值都为null)将null赋值给e.next ,此时key=3 的entry 和key=7 的entry 断开连接,同时将0x01 赋值给了newTable[3] ,最后用next的值也就是0x02 赋值给e,综上,第一次遍历完成后,整体效果会变成下图


key=7时的处理情况说明

 在标记8 处,e!=null,所以do,while循环继续 , 在标记3处:Entrynext = 0x04,在标记处4 ,算出来key=7时的索引应为 7 % 4= 3 ;在标记5处 e.next = newTable[i]; 相当于 e.next= 0x01,此时key=7的next 值会变为0x01,会指向key=3的节点,同时断开和key=5的关系, 在标记6处,newTable[i] = e; 会将0x02的的值赋值给newTable[3],此时map指向key=7的节点,再把next=0x04的值赋值给e 综上


key=5时的处理情况说明

 在标记8 处,e!=null,所以do,while循环继续 ,在标记3处:Entrynext = null,在标记处4 ,算出来key=5时的索引应为 5 % 4= 1 ;在标记5处,e.next = newTable[1]; e.next = null,在标记6处,newTable[i] = e;

则newTable[1] = 0x04,所以数组指向key=5,标记7处, 将e =null,此时不再满足 do while 循环条件退出,综上


最后,总结一下map1.7的扩容:就是遍历当前这个数组,拿到数组中的每一个entry,然后再遍历entry上的每一个节点,然后把每一个节点重新计算其在新数组上的位置,这句话其实在咱们如何扩容标题就已经告诉大家了哟~大家也只需要这么去回答面试官就好啦~


四、map1.8如何扩容

注意:oldCap是原来数组的长度

源码如下,并附上简单说明

for (int j = 0; j < oldCap; ++j) {
    Nodee;
    if ((e = oldTab[j]) != null) {
        oldTab[j] = null;
        if (e.next == null)
            newTab[e.hash & (newCap - 1)] = e;
        else if (e instanceof TreeNode)
            ((TreeNode)e).split(this, newTab, j, oldCap);
        else { // preserve order
            NodeloHead = null, loTail = null;
            NodehiHead = null, hiTail = null;
            Nodenext;
            do {
                next = e.next;
                // 通过源码我们发现 它会去判断oldCap & 出来的结果是否是0 
                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);
            //  根据是否0 ,来判断是否移动       
            if (loTail != null) {
                // 如果是0 ,不移动
                loTail.next = null;
                newTab[j] = loHead;
            }
            if (hiTail != null
                //如果是0 ,移动,并且移动为原来的oldCap这么大
                hiTail.next = null;
                newTab[j + oldCap] = hiHead;
            }
        }
     }
  }

分析流程

 变量说明:oldCap :就是原来的数组长度

 1.8 的扩容做法,是遍历数组上的链表数据, 并不是每个人都重新算一遍   只是去拿着这个当前遍历到这个值去 & oldCap 算出来是 0 或者是 算出来不是0 -> 去挪动元素  如果是 0 , 不挪动,就放在原来位置  如果非 0 , 就挪动, 挪动的位数是原来位置 + oldCap

情况一、当与oldCap计算出来结果是0时

 同学,此刻你有没有发现,最终的这个索引值到底有没有变化,其实原数组长度最高位对上去的那一位是否是个0 呢?如果你还不明白,那么我们接着往后看

 小总结

 是的,是看的没扩容之前的oldCap的最高位,如果我计算出来这个值是0 ,那么扩容没扩容实际上没有区别,如果你算出来的值不是0 ,那么就需要移动,需要移动多少呢,对,就是oldCap最高位的二进制

情况二、当与oldCap计算出来结果不是0时

为了让同学们更好的理解,我们再来模拟一个不是0 的情况 ,同学们注意看哦,我把hashCode 的从后向前数的5位从0 ,改成了1

                                                                                                                                                                                                                

小总结

你会发现,如果oldCap对上去的那一位是1的话,那么此时新容量的计算是要移动的,并且移动的位数就是用原来的长度+ 用新算法算出来的那个值 也就是10 + 16 = 26 确定它的位置

五、 总结

 好了,哥们门,如果以后面试官问你,map的扩容是怎么一回事,怎么答,你应该知道了吧,希望大家通过学习能有所收获



推荐阅读
  • 目录一、salt-job管理#job存放数据目录#缓存时间设置#Others二、returns模块配置job数据入库#配置returns返回值信息#mysql安全设置#创建模块相关 ... [详细]
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 本题探讨了在大数据结构背景下,如何通过整体二分和CDQ分治等高级算法优化处理复杂的时间序列问题。题目设定包括节点数量、查询次数和权重限制,并详细分析了解决方案中的关键步骤。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 本文介绍如何使用MFC和ADO技术调用SQL Server中的存储过程,以查询指定小区在特定时间段内的通话统计数据。通过用户界面选择小区ID、开始时间和结束时间,系统将计算并展示小时级的通话量、拥塞率及半速率通话比例。 ... [详细]
  • PHP 实现多级树形结构:构建无限层级分类系统
    在众多管理系统中,如菜单、分类和部门等模块,通常需要处理层级结构。为了高效管理和展示这些层级数据,本文将介绍如何使用 PHP 实现多级树形结构,并提供代码示例以帮助开发者轻松实现无限分级。 ... [详细]
  • 本文详细解析了Java中hashCode()和equals()方法的实现原理及其在哈希表结构中的应用,探讨了两者之间的关系及其实现时需要注意的问题。 ... [详细]
  • 本文介绍 SQL Server 的基本概念和操作,涵盖系统数据库、常用数据类型、表的创建及增删改查等基础操作。通过实例帮助读者快速上手 SQL Server 数据库管理。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 机器学习核心概念与技术
    本文系统梳理了机器学习的关键知识点,涵盖模型评估、正则化、线性模型、支持向量机、决策树及集成学习等内容,并深入探讨了各算法的原理和应用场景。 ... [详细]
  • 1.介绍有时候我们需要一些模拟数据来进行测试,今天简单记录下如何用存储过程生成一些随机数据。2.建表我们新建一张学生表和教师表如下:CREATETABLEstudent(idINT ... [详细]
author-avatar
君莫笑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有