七、JDK1.7中HashMap扩容机制

2023-10-27

导读

前面文章一、深入理解-Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍,上两篇文章二、Jdk1.7和1.8中HashMap数据结构及源码分析三、JDK1.7和1.8HashMap数据结构及源码分析-续 中我们分别对JDK1.7和JDK1.8中HashMap的数据结构、主要声明变量、构造函数、HashMap的put操作方法做了深入的讲解和源码分析。
四、深入理解Java中的HashMap「网易面试快答」文章中主要针对面试中常见的面试问题进行简单解答。
五、深入理解JDK1.7中HashMap哈希冲突解决方案六、深入理解JDK1.8中HashMap哈希冲突解决方案 中对HashMap中哈希冲突及减少哈希冲突的解决方案做详细的介绍,并通过源码加深大家的理解。

本篇文章我们将要介绍JDK1.7中HashMap的扩容机制及扩容过程中可能出现的死锁及数据丢失问题。

如果大家在面试中针对Java集合或者Java中的HashMap大家还有什么疑问或者其他问题,可以评论区告诉我。

简单介绍

JDK1.7—》哈希表,链表

JDK1.8—》哈希表,链表,红黑树— JDK1.8之后,当链表长度超过8使用红黑树。

非线程安全

0.75的负载因子,扩容必须为原来的两倍。

默认大小为16,传入的初始大小必须为2的幂次方的值,如果不为也会变为2的幂次方的值。

根据HashCode存储数据。

HashMap扩容机制-为什么负载因子默认为0.75f?

负载因子0.75 如果容量大大0.75则扩容为原来的两倍。
扩容因此 0.75
空间利用率和时间效率在0.75的时候达到了平衡。
在统计学上0.693是最佳的选择。然后可能更想着有空间利用率,而且在。Net语言中 hashmap的负载因子是0.7.

JDK1.7-根据条件判断是否对哈希表进行扩容

如果当前哈希表大小超过了阀值,并且新entry要插入的哈希桶的位置不为空
则把哈希表大小扩展为原来的两倍。
当哈希表中数据容量达到阀值,则使用一个新数组来获得一个更大的容量。
如果当前容量是允许的最大容量(2的30次幂),则该方法不会再继续扩大容量,
但是他会把负载门槛设置为Integer.MAX_VALUE.这只为了防止后续再进行扩容操作。

新传入的容量的值必须为2的n次幂,并且必须大于当前数组的容量。
如果当前数组容量已经允许的最大容量(2的30次幂),则新传入的值被忽略。

/**
当哈希表中数据容量达到阀值,则使用一个新数组来获得一个更大的容量。
如果当前容量是允许的最大容量(2的30次幂),则该方法不会再继续扩大容量,
但是他会把负载门槛设置为Integer.MAX_VALUE.这只为了防止后续再进行扩容操作。
 * Rehashes the contents of this map into a new array with a
 * larger capacity.  This method is called automatically when the
 * number of keys in this map reaches its threshold.
 *
 * If current capacity is MAXIMUM_CAPACITY, this method does not
 * resize the map, but sets threshold to Integer.MAX_VALUE.
 * This has the effect of preventing future calls.
 *新传入的容量的值必须为2的n次幂,并且必须大于当前数组的容量。
如果当前数组容量已经允许的最大容量(2的30次幂),则新传入的值被忽略。
 * @param newCapacity the new capacity, MUST be a power of two;
 *        must be greater than current capacity unless current
 *        capacity is MAXIMUM_CAPACITY (in which case value
 *        is irrelevant).
 */
void resize(int newCapacity) {
//把原哈希表数组赋值给oldTable
    Entry[] oldTable = table;
//把原哈希表容量赋值给oldCapacity
    int oldCapacity = oldTable.length;
//如果当前的哈希表容量已经达到允许的容量最大值(2的30次幂),则不再进行扩容
//且把当前哈希表的负载门槛设置为Integer的最大值。返回,跳过。
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }
//创建一个新的哈希数组,容量为新传入的容量值 
//该容量值必须是2的n次幂,且大于原数组容量大小
    Entry[] newTable = new Entry[newCapacity];
//开始把原哈希表数组数据转入新创建的哈希表数组中
//在开始转存前要先根据新的数组容量及相应的算法得出是否使用哈希干扰掩码
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
//转存完成后把新表内容放到HashMap的哈希表值中
    table = newTable;
//设置当前容量下的负载门槛
//(新容量 * 负载因子)的值与(HashMap允许的最大容量(2的30次幂)+1) 进行比较,
//取值小的那一个
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

哈希表数据转存:
把哈希表中的所有entry从当前的哈希表转入到新的哈希表中
HashMap扩容时避免rehash的优化
扩容迁移的时候要进行rehash –影响效率。

/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
	//获取新哈希表的容量
    int newCapacity = newTable.length;
//循环原哈希表
    for (Entry<K,V> e : table) {
//循环原Entry线性链表
        while(null != e) {
            Entry<K,V> next = e.next;
//根据是否启用rehash判断是否为每一个key生成新的哈希值
//如果当前entry的key等于null,则重新设置当前entry的哈希值为0
//如果不为null,则对当前entyr的哈希值根据哈希干扰因子(HashSeed)进行重
//新计算赋值
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
//根据新的哈希值和新的容量计算该entry应该存放的数组下标位置
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

我们对以上的语句使用一个示例进行解读:
1.分析图解:
1:为了方便计算,假设hash算法为key mod链表长度;
2:初始时数组长度2,key = 3, 7, 5 ,初始在表table[1]节点;3:然后resize后,hash数组长度为4

在这里插入图片描述
2.第一次while循环

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length; //newCapacity = 4;     
    for (Entry<K,V> e : table) { //第一次for循环数组下标为0 ; e == null 跳出while循环 ;第二次for循环数组下标为1;e = 3;   
        while(null != e) {    //第一次while循环 e = 3;e != null
            Entry<K,V> next = e.next;// next = e.next = 7;
            if (rehash) {  //暂时忽略rehash
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);  //新数组下标 i = (3 % 4) = 3;
            e.next = newTable[i];  //e.next = newTable[i] = newTable[3] = null;
            newTable[i] = e;  // newTable[3] = 3; 
            e = next;  //e = next = 7;
        }
    }
}

在这里插入图片描述
3.第二次while循环

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length; //newCapacity = 4;     
    for (Entry<K,V> e : table) { //第一次for循环数组下标为0 ; e == null 跳出while循环 ;第二次for循环数组下标为1;e = 3;   
        while(null != e) {    // 第二次while 循环 e = 7; e != null;
            Entry<K,V> next = e.next;// next = e.next = 5;
            if (rehash) {  //暂时忽略rehash
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);  //新数组下标 i = (7 % 4) = 3;
            //e.next = newTable[i] = newTable[3] = 3;  
            //这里要清楚newTable[3]的值已经是新数组中的3.
            //因此这一步的操作实际上是把当前的e(7)的next指针指向了3.  -也就是常说的头部插入法
            e.next = newTable[i];              
	   newTable[i] = e;  //newTable[3] = 7; 
            e = next;  //e = next = 5;  后面while循环及for循环以此类推
        }
    }
}

在这里插入图片描述
HashMap的转存使用头部插入法。
分析图解:
    1:为了方便计算,假设hash算法为key mod链表长度;
    2:初始时数组长度2,key = 3, 7, 5 初始在表table[1]节点;
    3:然后resize后,hash数组长度为4
在这里插入图片描述
如果不发生异常,正常结果为:
在这里插入图片描述

JDK1.7-Hashmap扩容死锁问题

JDK1.7—>-两个线程同时并发的对原数组扩容。由于链表都使用头插法,两个线程在用指针指向后,会形成循环链表。 然后再新数据进入的时候,会先从链表上找是否存在对应的key。然后在循环链表中一直死循环,如法插入。

1:假设线程A在某处挂起

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            //线程A在此处挂起
            newTable[i] = e;
            e = next;
        }
    }
}

在这里插入图片描述当A挂起后,线程B正常执行完
在这里插入图片描述由于线程B已经执行完毕,根据Java内存模型,现在newTable和table中的Entry都是主存中最新值:7.next=3,3.next=null。
此时切换回线程A上,在线程A挂起时继续执行
newTable[i]=e ----> newTable[3]=3
e=next ----> e=7
在这里插入图片描述

继续下一次循环,e=7

next=e.next ----> next=3【从主存中取值】
e.next=newTable[3] ----> e.next=3【从主存中取值】
newTable[3]=e ----> newTable[3]=7
e=next ----> e=3

e不为空继续下一次循环 e=3
next=e.next ----> next=null
e.next=newTable[3] ----> e.next=7 即:3.next=7
newTable[3]=e ----> newTable[3]=3
e=next ----> e=null

此次循环后3.next = 7 但上一步 7.next =3 行成环行链表

在这里插入图片描述 在后续操作中只要涉及轮询hashmap的数据结构,就会在这里发生死循环

JDK1.7-HashMap扩容死锁问题复现


import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * JDK1.7 HashMap死锁问题复现
 */
public class TestHashMapThread implements Runnable{
    //为了尽快扩容,设置初始大小为2
    private static Map<Integer,Integer> map = new HashMap<>(2);

    private static AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public void run() {
        while (atomicInteger.get() < 1000000){
            map.put(atomicInteger.get(),atomicInteger.get());//往线程中插入值
            atomicInteger.incrementAndGet();//递增
        }
    }

    public static void main(String[] args){
        for(int i = 0; i < 10 ; i++){
            //启动十个线程去做处理
            new Thread(new TestHashMapThread()).start();
        }
    }
}

JDK1.7-HashMap扩容数据丢失问题

其次,1.7中扩容还会出现数据丢失
模拟另外一种情况
在这里插入图片描述同样线程A在固定位置挂起
在这里插入图片描述
线程B完成扩容
在这里插入图片描述

同样注意由于线程B执行完成,newTable和table都为最新值:5.next=null。
此时切换到线程A,在线程A挂起时:e=7,next=5,newTable[3]=null。
执行newtable[i]=e,就将7放在了table[3]的位置,此时next=5。接着进行下一次循环:
e=5
next=e.next ----> next=null,从主存中取值
e.next=newTable[1] ----> e.next=5,从主存中取值
newTable[1]=e ----> newTable[1]=5
e=next ----> e=null
将5放置在table[1]位置,此时e=null循环结束,3元素丢失,并形成环形链表。并在后续操作hashmap时造成死循环。
在这里插入图片描述

往期文章链接

Java集合

一、深入理解-Java集合初篇

二、Jdk1.7和1.8中HashMap数据结构及源码分析

三、JDK1.7和1.8HashMap数据结构及源码分析-续

四、深入理解Java中的HashMap「网易面试快答」

五、深入理解JDK1.7中HashMap哈希冲突解决方案

六、深入理解JDK1.8中HashMap哈希冲突解决方案
Java-IO体系

一、C10K问题经典问答
二、java.nio.ByteBuffer用法小结
三、Channel 通道
四、Selector选择器
五、Centos-Linux安装nc
六、windows环境下netcat的安装及使用
七、IDEA的maven项目的netty包的导入(其他jar同)
八、JAVA IO/NIO
九、网络IO原理-创建ServerSocket的过程
十、网络IO原理-彻底弄懂IO
十一、JAVA中ServerSocket调用Linux系统内核
十二、IO进化过程之BIO
十三、Java-IO进化过程之NIO
十四、使用Selector(多路复用器)实现Netty中Reactor单线程模型
十五、使用Selector(多路复用器)实现Netty中Reactor主从模型
十六、Netty入门服务端代码
十七、IO进化过程之EVENT(EPOLL-事件驱动异步模型)

如需了解更多更详细内容也可关注本人CSDN博客:不吃_花椒

Java集合还需要学习的内容

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

七、JDK1.7中HashMap扩容机制 的相关文章

  • 使用 java 删除 XML 根的子级

    这是我的 xml 文件
  • 不支持的字段:将瞬间格式化为日期 ISO 时的年份[重复]

    这个问题在这里已经有答案了 我正在尝试将 Instant 格式化为 ldap 日期 ISO8601 但在 f format Instant now 处失败 String input 20161012235959 0Z DateTimeFor
  • 类型已知,但方法指的是缺失类型

    我对 java 和 Eclipse 不太有经验 但遇到以下问题 我正在写类似的东西 Point3D myPoint myClass myMethod arg 我收到错误 方法 myMethod myType arg 引用缺失的类型 Poin
  • 是否可以使用 Java 读写 Parquet,而不依赖 Hadoop 和 HDFS?

    我一直在寻找这个问题的解决方案 在我看来 如果不引入对 HDFS 和 Hadoop 的依赖 就无法在 Java 程序中嵌入读写 Parquet 格式 它是否正确 我想在 Hadoop 集群之外的客户端计算机上进行读写 我开始对 Apache
  • Apache Thrift Java-Javascript 通信

    我正在编写一个基于 Apache Thrift 的 Java 服务器 它将从 Javascript 客户端接收数据 我已经完成了 Java 服务器 但问题是我可以获得 Javascript 客户端的工作示例 我无法找到一个好的示例 构建文档
  • 在 Eclipse 3.5 上安装旧版 TestNG 插件时出现问题

    我正在尝试在 eclipse 3 5 上安装 TestNG 5 11 并获得以下信息 eclipse buildId unknown java version 1 6 0 19 java vendor Sun Microsystems In
  • 如何将本机数据库运算符 (postgres ~) 与 JPA 标准生成器一起使用?

    我使用 JPA 2 0 标准构建以下查询 简化 select n from notif n where n message b la 我正在使用 postgresql 数据库 我真的需要 运算符 而不是像 我可以使用与 CriteriaBu
  • 未注入带有 JPA2 的 Apache Ignite 2.7 IgniteRepository

    使用在 Web 上建立的 guildes 我使用 Spring Data JPA 2 应用程序制作了简单的 Spring Boot 2 仅在 2 7 版本中才向 Apache Ignite 添加了 Spring Boot JPA 2 支持
  • Maven WebApp META-INF context.xml

    我正在使用 Maven 3 并且尝试在 webapp 文件夹下添加 META INF 文件夹 所以我正在尝试执行以下操作 src main webapp META INF context xml WEB INF 下面是我的 POM 文件
  • 使用 kryo 注册课程的策略

    我最近发现了 kryonet 库 它非常棒并且非常适合我的需求 然而 我遇到的一个问题是制定一种好的策略来注册所有可以转移的类 我知道我可以在每个对象中编写一个静态方法 该方法将返回它使用的所有类的列表 但我真的不想这样做 为了我自己的时间
  • 绘制平滑曲线

    我想创建更平滑的曲线 而不仅仅是线角 这是我现在画的图 这是我的代码 case FREEHAND float pts float ptk ptk new float 2 imageMatrix invert inv if mCurrentS
  • for循环中更新JLabel的问题

    我的程序的想法是从之前在其他 JFrame 中保存的列表中选择一个名称 我想在标签中一个接一个地打印所有名称 它们之间有很小的延迟 然后停在其中一个名称上 问题是lbl setText String 如果有多个则不起作用setText co
  • 如何在 Eclipse 中获得完全限定的类名?

    有没有一种快速方法可以在 Eclipse 中单击 Java 类并获取其完全限定名称 或将其复制到剪贴板 2016年6月29日编辑 正如 Jeff 所指出的 您只需要执行以下第二步 1 Double click on the class na
  • 在Java中如何将字节数组转换为十六进制?

    我有一个字节数组 我希望该数组的每个字节字符串转换为其相应的十六进制值 Java中有没有将字节数组转换为十六进制的函数 byte bytes 1 0 1 2 3 StringBuilder sb new StringBuilder for
  • 错误膨胀类 android.support.design.widget.NavigationView [启动时崩溃]

    该应用程序应该有一个导航抽屉 可以从左侧拉出并显示各种活动 但是一旦将导航栏添加到 XML Activity homescreen 文档中 应用程序一启动就会崩溃 主屏幕 java package com t99sdevelopment c
  • jDBI中如何进行内查询?

    我怎样才能在 jDBI 中执行这样的事情 SqlQuery select id from foo where name in
  • 接口是否像对象一样对待?

    为什么下面的代码可以工作 interface I class A implements I public String toString return in a class B extends A public String toStrin
  • 为什么不能在 if 语句中声明变量?

    以下 Java 代码无法编译 int a 0 if a 1 int b 0 if a 1 b 1 为什么 不能有任何代码路径导致程序将 1 分配给b无需先声明 我突然想到b的变量范围可能仅限于第一个if声明 但后来我不明白为什么 如果我实在
  • 从 InputStream 中删除换行符

    我喜欢从一个文件中删除所有换行符 对于 n 和 r n java io InputStream 在读取文件时 相应的方法如下所示 param target linkplain File return linkplain InputStrea
  • Java时区混乱

    我正在运行 Tomcat 应用程序 并且需要显示一些时间值 不幸的是 时间快到了 还有一个小时的休息时间 我调查了一下 发现我的默认时区被设置为 sun util calendar ZoneInfo id GMT 08 00 offset

随机推荐

  • PAT乙级1037 在霍格沃茨找零钱 (20分)

    pragma warning disable 4996 include
  • C#中 将字符串数组中的内容按照顺序进行反转

    Using System namespace shuzu class Pragram static void Main string args 将字符串数组中的内容按照顺序进行反转的两种方式 方式一 string StrList 你好 世界
  • Cocos 常用功能介绍

    学习来源链接 Cocos Creator零基础小白超神教程 cocos思想 cocos2d的编程最重要的是在于继承 一个对象继承自一个类 cocos creator的编程是每一个对象都是一个节点 在节点中可以挂载组件 一个节点中可以挂载无数
  • 夏普比率计算

    夏普比率的计算公式为 s h a r p e ThickSpace r a
  • 一文理解主数据和参考数据

    如果你准备要开展推动数据治理或者是数据质量的项目 那么你就有可能会听说到几个词 主数据和参考数据 一开始听到主数据这一词听起来就很高大上 而且非专业人士肯定不理解 即便是从事数据行业的朋友也很难参透 这一小节将会解答如下疑惑 1 什么是主数
  • 解决安装SolidWorks以后原来Altium designer创建的PCB、SCH以及工程文件图标改变,双击无法用Altium Designer打开的问题。

    第一步 打开Altium designer的系统设置图标 如下图 第二步 点击 system 如下图 第三步 点击 File Types 如下图所示 第四步 在右侧的列表中把pcb pcbdoc sch schdoc以及PrjPcb等前面方
  • c#数据类型

    数据类型 值类型 int float bool sizeof 查询变量占内存大小 一字节 8比特位 float 单精度浮点数 doublle 结构体 struct Student 访问类型 public公开 private类或结构体内部 i
  • bootstrap知识总结

    Bootstrap框架 一 Bootstrap简介 Bootstrap是一个用于快速开发Web应用程序和网站的前端框架 Bootstrap是基于HTML CSS JavaScript的 二 Bootstrap 特点 1 跨设备 跨浏览器 可
  • Angular4之动画

    在angular里面 动画的本质 是在一定时间 由一个状态转换到另一个状态 期间的过渡效果就是显示出来就是动画 例如 import Component Input from angular core import trigger state
  • 基于MCGS与PLC的四路抢答器

    实验要求 四路抢答器每一位抢答的选手都有一个抢答按钮 1号抢答按钮为SB1 2号抢答按钮为SB2 3号抢答按钮为SB3 4号抢答按钮为SB4 以及各位抢答选手的指示灯 主持人的位置设有抢答开始按钮SB5以及清零按钮SB6 另外系统中 有一个
  • 读《影响力》西奥迪尼---笔记

    读 影响力 西奥迪尼 笔记 1 本书框架 包括互惠 承诺与一致 社会认同 喜爱 权威 稀缺六大影响力武器 2 文书意图 从原理 成因上讲述这六个影响力武器对我们的影响 以及商家或者其他想要获利的如何利用影响力武器获取利益 意图说服我们如何拒
  • dvwa靶场通关(十二)

    第十二关 Stored Cross Site Scripting XSS 存储型xss low 这一关没有任何防护 直接输入弹窗代码 弹窗成功 medium 先试试上面的代码看看 有没有什么防护 发现我们的script标签不见了 应该是被过
  • 在JSP编译的时候,服务器内部做了什么?

    在JSP第一次获得请求时 不管请求来自于客户端浏览器还是服务器上的servlet JSP文件将被JSP引擎 JSP engine 转换成为一个servlet 而这个引擎本身也是一个servlet 在JSWDK 它就是 JspServlet
  • OpenCV - Mat、滤波、卷积的实现

    1 Mat数据类型 创建图像 Mat M 2 2 CV 8UC3 Scalar 0 0 255 改变图像尺寸 M create 4 4 CV 8UC2 快速创建图像的集中方法 Mat E Mat eye 4 4 CV 64F Mat F M
  • 子网掩码详解

    IP地址 IP地址被用来给Internet上的电脑一个编号 大家日常见到的情况是每台联网的PC上都需要有IP地址 才能正常通信 我们可以把 个人电脑 比作 一台电话 那么 IP地址 就相当于 电话号码 而Internet中的路由器 就相当于
  • C++设计模式

    设计模式有 6 大设计原则 单 职责原则 就 个类 应该仅有 个引起它变化的原因 开放封闭原则 软件实体可以扩展 但是不可修改 即 对需求 对程序的改动可以通过增加代码来完成 但是不能改动现有的代码 代换原则 个软件实体如果使 的是 个基类
  • HTML和CSS实现京东登录页面(html,css代码详解)

    HTML代码 基本布局 QQ 2248557717 下载链接地址 https download csdn net download dwjdj 15807158
  • STM32 C语言使用 memset清空结构体 导致改变其他结构体数据的问题

    首先 在C语言中 清空结构体的方法 我们一般会采用 memset函数 其原型是 void memset void ptr int value size t num 函数功能 填充内存块 将ptr指向的内存块的前num个字节设置为指定值 va
  • 2021年华中杯A题(马赛克瓷砖选色问题)详细分析

    目录 一 基本介绍 1 1 题目描述 1 2 待解决问题 二 问题分析与求解 2 1 问题一分析与求解 2 2 问题二分析与求解 2 3 问题三分析与求解 三 完整代码 四 总结 一 基本介绍 1 1 题目描述 马赛克瓷砖是一种尺寸较小 常
  • 七、JDK1.7中HashMap扩容机制

    导读 前面文章一 深入理解 Java集合初篇 中我们对Java的集合体系进行一个简单的分析介绍 上两篇文章二 Jdk1 7和1 8中HashMap数据结构及源码分析 三 JDK1 7和1 8HashMap数据结构及源码分析 续 中我们分别对