例子1:

      HashMap<Integer, Integer> map  = new HashMap<>(16, 2);for (int i = 0; i < 40; i++) {map.put(i, i);}

1、创建map对象长度是16,阈值为32的map对象。阈值的计算16*2=32,阈值的作用是,, 当向map对象添加超过32条数据时,也就是当添加到底33条数据时,要进行扩容。如果不设置默认加载因子是0.75,默认阀值是12(默认长度16*0.75=12)。

负载因子的作用是,它决定了hashmap数据的密度,负载因子越小,越容易出现扩容,发生碰撞的概率越小,数组中的链表越短,数组利用率也低,浪费空间。负载因子越大,发生碰撞的概率越高,出现扩容的的概率也越高,性能下降。

2、map在添加数时它的key是不能重复,使用的其实也是set来存,是无序的,不可重复的。添加路程,首先会创建一个长度是16的node类型的数组,计算要添加这个数据的hashcode,然后算出这个hashcode对应在内存中存储数据的索引,判断当前位置是否为空,如果为空,直接添加。如果不为空,会先判断当前要添加的hashcode是否等于存储在内存中这个节点的hashcode是否相等,如果hashcode不相等那么添加数据成功,如果相等,使用equals方法比较要添加的key和已经存过的key,如果这两个key相等,那么就替换已经存在内存中的value,如果两个的key不相等,那么会添加在已经添加在内存的这个节点的下面,以链表的形式添加成功。

源码解析:

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {/*** tab存储底层的table数组。* p 作用是作为指标,指向底层的tab也就是底层的Node<K,V>[] table中的某一个node,map底层存数据用的就是数组结构的node,和红黑树。* n 当前底层node数组的长度* i 只有在初始化的时候才会用到*/Node<K,V>[] tab; Node<K,V> p; int n, i;//如果底层的table等于null或者长度等于0,那么就是对map对象初始化的操作,因为,java1.8之后 不会自动在内存中创建一个空间1、在创建一个空的map对象时( new HashMap<>() ):创建map对象并没有在内存中开辟一个空间,而是在创建的时候创建了一个空的数组。只有在向mapput数据时才会去创建一个长度为16加载因子是0.75,阀值是12,阀值等于长度*加载因子。2、创建带有长度和阀值的对象(new HashMap<>(16, 100)),那么就会使用创建时传过来的长度和阀值。根据就不同的构造方法创建不同的map对象。*/if ((tab = table) == null || (n = tab.length) == 0)//判断1//初始化map对象时,就是上面描述的情况1.初始化一个长度是16阀值是0.75的数组n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)//判断2/*关于 i = (n - 1) & hash的计算* 以n等于16为例,传过来的hash在0到40为例* 那么i=15* i&0=1111&0000=0000  0000的十进制是0,其中1111是15的二进制,0000是0的二进制,&与运算,只有两个都是1的时候结果才是1* i&1=1111&0001=0001  0001的十进制是1* i&2=1111&0010=0010   0010的十进制是2* 以此类推。向tab的第一个node添加,第二个node加,第三个node加...第十五个node。** 该判断主要的作用是向tab数组中的添加数据。当向tab中添加到tab的长度时就不走这里了,也就是添加到底15个时,由于下标是从0开始的* */tab[i] = newNode(hash, key, value, null);else {//既不是map的初始化,并且数组已经加到数组的长度了,但是并没有到设置的阀值时,那么继续添加的数据就会以链表的形式存储。/*** e 是tab中node的指标,因为node是链表的形式,它也可以和另外的node连接起来形成链表* k 数据的key*/Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//p的赋值在上面的p = tab[i = (n - 1)这里,如果tab中第0条数据的hash等于要向map添加时穿过来的key或者这两个的key相等//通俗讲就是map对象之已经存过和这次要存的key相等了,已经有相同的key了,那么就把当前tab数组对应的这个node赋给ee = p;else if (p instanceof TreeNode)//判断是否是红黑树e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//这里主要是对tab中的每个node再添加一个node,把这两个node连起来,形成链表。for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {//如果tab中的第i个node没有下一个node,那么就把这次要添加的数据加到tab中第i个node的下一个node/** 图形 链表* tab * I--------------I* I (node0)    I---->node4* I  (node1)     I---->node5* I  (node3)     I---->node6* I--------------I* 类似这里的node4就是存这次要添到node下一个node的数据*/p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//如果node0->node4->node7....node(n)->这条链子的长度超过8,也就是到第9个时,就会使用红黑树,提高查询效率treeifyBin(tab, hash);break;}//如果子链中有和这次要添加的数据的key相等了,和上面一样,覆盖掉之前向map添加的的valueif (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//继续循环这个子链下的nodep = e;}}if (e != null) { // existing mapping for key//就是上面的第一个判断的结果,map中已经存在相同的key了//拿到这个node对应的valueV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)//将这次要添加的value替换之前的value,所以是经常遇到的,向map添加相同key,不同value时,最后添加的那条会把之前的value覆盖掉。e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)//当向map添加超过阀值时,进行扩容resize();afterNodeInsertion(evict);return null;}

tab的图形:

扩容

   final HashMap.Node<K,V>[] resize() {//把底层的数组table赋值给oldTab数组HashMap.Node<K,V>[] oldTab = table;//获取底层之前数组的长度int oldCap = (oldTab == null) ? 0 : oldTab.length;//获取之前的阀值int oldThr = threshold;//新的数组长度和阀值int newCap, newThr = 0;//如果原来的数组长度大于0,并不是第一次初始化使用的if (oldCap > 0) {//如果旧的数组长度大于最大的内存if (oldCap >= MAXIMUM_CAPACITY) {//重新赋给新的阀值,这样的作用是在超大数据量时,使用久的临界值算出来的阀值太小了,会出现频繁的扩容,消耗资源和时间,//用新的阀值算出来的临界值会比之前大,这样就避免出现扩容,这样会出现每个节段的连会很长。threshold = Integer.MAX_VALUE;return oldTab;}//如果旧的长度还没有超过最大值,扩容,newCap=原来数组长度*2(也就是下面的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 thresholdnewCap = oldThr;else {               // zero initial threshold signifies using defaults//这个是在初始化容器的时候使用的//设置默认数组长度为16newCap = DEFAULT_INITIAL_CAPACITY;//设置初始阀值等于0.75*默认的长度16=12newThr = (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"})HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap];//存数据的数组还是用原来的,只是先把这个数组设置为空,才方便扩容,原来的数组在上面已经赋给oldTab了table = newTab;if (oldTab != null) {//循环旧的数组for (int j = 0; j < oldCap; ++j) {//设置一个节点指针,用来存旧数组的每个节点的数据HashMap.Node<K,V> e;//在这里把底旧数组的第j个数据赋给eif ((e = oldTab[j]) != null) {//如果旧数组的第j个数据不为空 ,先把旧数组的这个节点设置为空,这个节点原来的数据已经赋值给e了,给垃圾回收。oldTab[j] = null;//如果旧数组的第j个数据的下一个节点数据为空,就是当前这个节点没有与它相连接的节点if (e.next == null)//那么就把第j个节点的数据赋值给新数组newTab[e.hash & (newCap - 1)] = e;else if (e instanceof HashMap.TreeNode)((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);else { // preserve order// 如果旧数组的第j个数据既不为空也不是使用红黑树,那么就进行拷贝HashMap.Node<K,V> loHead = null, loTail = null;HashMap.Node<K,V> hiHead = null, hiTail = null;HashMap.Node<K,V> next;do {//下面这个循环是把底j个数据包括它自己和与它相连接的节点拷贝到新的容器,打个断点看把next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.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;}

关于hashMap扩容相关推荐

  1. Btrace详细指南(JDK7,监控HashMap扩容)

    背景     JAVA中如何排查疑难杂症,如何动态获取应用信息,我们有BTrace! PS:集团有大杀器arthas,这里我们先从最原始最广泛的BTrace开始,后面可以玩玩Greys(开源,强于BT ...

  2. hashmap扩容 面试_HashMap面试,看完这一篇就够了(上)

    以下HashMap源码的解析都是基于java8来讲解的. HashMap的结构是数组加链表的形式(jdk7中也是),在java8中引入了红黑树,由于红黑树的时间复杂度是O(log n),引入红黑树是为 ...

  3. hashmap扩容机制_图文并茂:HashMap经典详解!

    点击上方 Java后端,选择 设为星标 优质文章,及时送达 代码中的注解多看几遍,其中HashMap的扩容机制是要必懂知识!结合图片一起理解! 什么是 HashMap? HashMap 是基于哈希表的 ...

  4. hashmap扩容线程安全问题_HashMap在1.7 1.8中的线程安全问题

    HashMap的线程不安全主要体现在下面两个方面: 在JDK1.7中,当并发执行扩容操作时会造成环形链和数据丢失的情况. 在JDK1.8中,在并发执行put操作时会发生数据覆盖的情况. ? ? 常被问 ...

  5. hashmap扩容机制_图文并茂,HashMap经典详解!

    Java面试笔试面经.Java技术每天学习一点 公众号Java面试 关注我不迷路 作者:feigeswjtu 来源:https://github.com/feigeswjtu/java-basics ...

  6. hashmap扩容_面试官问:HashMap在并发情况下为什么造成死循环?一脸懵

    这个问题是在面试时常问的几个问题,一般在问这个问题之前会问Hashmap和HashTable的区别?面试者一般会回答:hashtable是线程安全的,hashmap是线程不安全的. 那么面试官就会紧接 ...

  7. 聊一聊不同技术栈中hashmap扩容机制

    前言 hash简介 作为后端开发,说HashMap是我们最经常接触到的数据结构都不为过,而HashMap如其名最主要依赖的算法就是hash散列算法来存储和读取数据.         以关键码值K为自变 ...

  8. HashMap 扩容 加载因子

    HashMap 扩容 加载因子 最近在看HashMap源码,对于扩容因子=0.75感到很费解,为什么在用了75%的容量的时候就要进行扩容呢?数组中明明还有25%的空间没有使用.为什么不等到数组几乎满了 ...

  9. HashMap扩容改进分析

    HashMap扩容 JDK1.8以后对之前版本的HashMap扩容多线程场景下的死锁的问题进行了解决.采用高低位拆分转移方式,避免了链表的产生. resize()方法扩容部分 for (int j = ...

  10. Java之HashMap系列--HashMap扩容的原理

    原文网址:Java之HashMap系列--HashMap扩容的原理_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍Java的HashMap是如何扩容的. 重要大小 类 初始容量 最大容量 扩容 ...

最新文章

  1. 智源论坛报名 | 智能体系架构与芯片
  2. 小型直流电机内部结构
  3. Android在桌面上添加开关,多键开关 Andromax v1.1.7
  4. rtc关机闹钟6 AlarmManagerService研究
  5. How CRM_JEST is influenced by status change in WebUI
  6. 空间索引不能用analyze进行分析
  7. 常见的预设分栏包括_计算机应用基础_实训项目二Word综合应用
  8. Android 里的adb命令
  9. 2021年 考研数学一 第17题
  10. 老年人健康管理系统技术开发
  11. 遗传算法工具箱_含约束条件的遗传算法在连续催化重整优化操作中的应用
  12. git官网下载速度太慢解决方法
  13. pe和linux一起安装到移动硬盘,PE安装在移动硬盘的详细教程
  14. QPalette类详细使用方法
  15. word无法显示下划线
  16. PKU2506Tiling
  17. java 实现ps功能_JS实现在线ps功能详解
  18. python图像数据分析,【笔记】python数据分析——应用案例之图像负片
  19. java多态优化多个if_脑壳疼!代码中那么多“烦人”的if else
  20. 推荐系统中的用户偏好

热门文章

  1. 关于DHCP服务器的offer与ack阶段是单播还是广播的研究【转】
  2. TF卡只读数据三年后的变化
  3. Liunx系统编程篇—进程通信(五)信号
  4. 【Java二维数组】(超详解)
  5. Win10笔记本只能搜到以太网查不到WiFi的排查步骤与最终解决方法
  6. 从“数据合规官”到“安全岛”,将数据的安全合规进行到底
  7. java共享充电宝管理系统ssm框架
  8. MyBatis工作原理
  9. 制作彩色艺术效果图(每天一个PS小项目)
  10. angular使用@input子组件获取父组件的数据和方法