仔细瞄一下HashMap是怎么干活的
以下分析基于jdk11.0.2
1. 创建HashMap时发生了什么?
HashMap(),HashMap(int initialCapacity),HashMap(int initialCapacity, float loadFactor)。这三个方法都直接或间接地会初始化loadFactor(加载因子)和threshold(扩容阈值)。其中threshold=capacity*loadFactor。
1.1 threshold如何确定?
当调用HashMap()创建HashMap时,threshold的值会在第一次resize()时赋值。由DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY可知threshold=0.75*16=12
当调用HashMap(int initialCapacity)/HashMap(int initialCapacity, float loadFactor) 创建HashMap时,threshold由 loadFactor*tableSizeFor(int cap) 计算得出。
2. 调用put(K key, V value)时发生了什么?
int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
该方法首先调用了hash()方法获取key对应的hash值,然后调用putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)…
2.1. hash(Object key)做了些什么?
该方法将key的hashCode的高16位与低16位进行了一次异或位运算(hashCode为32bit的int类型)。v1.8+中该方法的实现较之前版本更容易发生hash碰撞(之前版本为4次异或运算),这是权衡性能和红黑树的优化…
2.2. putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict)做了什么?
该方法除了供put()调用,也提供给putIfAbsent()调用。在此暂时讨论put()调用的情况,即 boolean onlyIfAbsent=false; boolean evict=true;
下面列出用无参构造函数new HashMap()创建的对象进行put的几种情况:
2.2.1. 第一次put时,执行步骤如下:
1. 执行resize(),将map中的table初始化为大小为DEFAULT_INITIAL_CAPACITY的Node数组;threshold赋值为DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY。
2. 使用hash, key, value创建Node节点,作为链表的头节点存于table[i]中,下标为 i = (n - 1) & hash 。
2.2.2. 当put后table[]内节点数<=threshold(默认threshold=12,而此时table[].size也就是capacity应为16,这两个值会随着resize更新)时,执行步骤如下:
1. 找到hash对应table[]中的链表/树
2. 当table[]存的是链表时,把key-value存入链表尾节点或替换key对应节点的value值,并判断链表长度是否>TREEIFY_THRESHOLD(默认值8),如果是则调用treeifyBin()。调用treeifyBin()时会判断是否需要将该链表转为树。当table[].size>=MIN_TREEIFY_CAPACITY会转为树,否则只是resize()扩容;而当table[]存的是树时,调用TreeNode.putTreeVal()在树中存入/替换。
2.2.3. 当put后table[]内节点数>threshold时:
执行完2.2.2的操作后,执行执行resize():capacity翻倍(<<1),threshold也重新计算。
画了张流程图用来精简表示putVal:

3. 调用resize()时发生了什么?
在putVal途中调用有两种情况下HashMap会调用resize()进行扩容和table[]数据迁移(迁移几率50%):
3.1. 第一次调用putVal后调用resize():
3.1.1. 未指定initialCapacity或loadFactor值:
创建table[],大小为DEFAULT_INITIAL_CAPACITY(默认值16);赋值threshold=DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY(默认值12)。
3.1.2. 已指定initialCapacity或loadFactor值:
创建容量为tableSizeFor(initialCapacity)的table[];给扩容阈值赋值 threshold = loadFactor * tableSizeFor(initialCapacity)。
简单说明一下tableSizeFor(int cap)函数:返回值为大于等于cap且与cap差值最小的2^n的值。例如3->4,4->4,9->16,65->128。
3.3. table[]内节点数>threshold时,执行步骤如下:
3.3.1. 重新计算table[]容量capacity和扩容阈值threshold,值皆为原值的2倍(<<1),创建新table[capacity]
3.3.2. 遍历原table[]中的链表/树,
当链表为单节点时:将该节点放至新table[],下标为hash&(capacity-1) ;
当链表为多节点时:遍历该链表并分离出一条需要移动位置的链表,将2条链表放至新table[]。可根据hash&oldCapacity==0判断Node是否需要移动;
当链表为红黑树时:调用TreeNode.split()将树拆分/移动。当树的大小<=UNTREEIFY_THRESHOLD(默认6)时会退化成链表。
仔细瞄一下HashMap是怎么干活的的更多相关文章
- HashMap 源码详细分析(JDK1.8)
一.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap.HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现.HashMap 允许 null 键和 null 值, ...
- Hashmap误区
HashMap简介 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.HashMap 继承于AbstractMap,实现了Map.Cloneable.java.io.Ser ...
- centos7下Redis-Sentinel安装和配置
一.Redis的安装 1.从官网https://redis.io/download下载最新的stable版本(也可以下载unstable版本)redis-4.0.9.tar.gz. 2.上传到Cent ...
- NEXYS 3开发板练手--LED与数码管时钟
做科研的时候从学校拿到一块基于Xilinx公司Spartan-6主芯片的FPGA开发板,因为之前一直在用Altera公司的FPGA,一开始接触它还真有点不太习惯.但毕竟核心的东西还是不会变的,于是按照 ...
- JAVA源码分析-HashMap源码分析(一)
一直以来,HashMap就是Java面试过程中的常客,不管是刚毕业的,还是工作了好多年的同学,在Java面试过程中,经常会被问到HashMap相关的一些问题,而且每次面试都被问到一些自己平时没有注意的 ...
- HashMap源码阅读笔记(基于jdk1.8)
1.HashMap概述: HashMap是基于Map接口的一个非同步实现,此实现提供key-value形式的数据映射,支持null值. HashMap的常量和重要变量如下: DEFAULT_INITI ...
- [Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联.
注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享. 原文地址: http://blog.csdn.net/zhanger ...
- HashMap封装的数据用循环快速添加进list中产生的数据集全部相同的问题
一.问题概述 在一次使用SimpleAdapter时,Data需要使用传入一条数据(Image.Text),该数据条使用HashMap封装.在用HashMap封装的数据用循环快速添加进list中产生了 ...
- [转]不正当使用HashMap导致cpu 100%的问题追究
以前项目中遇到类似业务,但使用的是CurrentHashMap,看到这篇文章,转载记录,警示自己. 以下内容转自: 转载自并发编程网 – ifeve.com(http://ifeve.com/hash ...
随机推荐
- 禁止DataGridView控件中添加和删除行
实现效果: 知识运用: DataGridView控件的AllowUserToAddRows AllowUserDeleteRows和ReadOnly属性 实现代码: private void btn_ ...
- 11.使用while和for循环分别打印字符串s=’asdfer’中每个元素
1).for循环 s = 'asdfer' for i in s: print(i) 2).while循环 s = 'asdfer' while 1: print(s[index]) index += ...
- ajax $.post 一直报 Forbidden (CSRF token missing or incorrect.)
由于后台整合类视图代码,所以修改了写法,完了之后用下面的写法写的post请求都报 403 error $.post( "{% url 'test_record:select_node_pag ...
- attachEvent和addEventListener 的使用方法和区别
attachEvent方法,为某一事件附加其它的处理事件.(不支持Mozilla系列)addEventListener方法 用于 Mozilla系列document.getElementById(&q ...
- kmp和hash 字符串处理 哈希表
来自http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html 并进行自己的简单整 ...
- CF #552 div3
A - Restoring Three Numbers CodeForces - 1154A Polycarp has guessed three positive integers aa, bb a ...
- Linux-CentOS6.9启动流程排错
1 . CentOS6.9启动流程 POST 加电自检,是BIOS功能的一个主要部分.负责完成对CPU.主板.内存.硬盘子系统.显示子系统.串并行接口.键盘.CD-ROM光驱等硬件情况的检测. Boo ...
- tomcat报错:java.io.IOException: 您的主机中的软件中止了一个已建立的连接。
tomcat报错: org.apache.catalina.connector.ClientAbortException: java.io.IOException: 您的主机中的软件中止了一个已建立的 ...
- GTX 750等低配显卡如何玩转Deepfakes?
这里说的Deepfakes软件还是DeepFaceLab,人工智能换脸,是使用深度学习方法来实现的.而深度学习程序对电脑配置要求是非常高的,尤其是跑模型这个环节.很多低配电脑,根本就跑步起来.比如像G ...
- 有关Kali更新问题的解决方法。
近期更新源遭遇诸多不顺,无非是各种依赖问题的报错夹杂着各种稀奇古怪的问题,不过既然是玩Linux,就要做好处理各种疑难杂症的准备.经过了这几天的不断尝试,今天终于解决了更新出错的问题. 本人更新源出现 ...