仔细瞄一下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 ...
随机推荐
- .vue公共组件裁减导航
场景: 有一个公共头部和底部,vue搭建的框架,在app.vue里写的公共方法,首页是个登录页面,不需要公共部分,在这基础上进行公共部分的显示隐藏. 即注册页.登录页.404页面都不要导航 代码: ( ...
- Redis的安装以及spring整合Redis时出现Could not get a resource from the pool
Redis的下载与安装 在Linux上使用wget http://download.redis.io/releases/redis-5.0.0.tar.gz下载源码到指定位置 解压:tar -xvf ...
- GCD之dispatch queue
GCD之dispatch queue iOS中多线程编程工具主要有: NSThread NSOperation GCD 这三种方法都简单易用,各有千秋.但无疑GCD是最有诱惑力的,因为其本身是appl ...
- PHP计算两个日期相差的年月日时分秒
$start_time = '2017-09-06 15:12:20'; $end_time = '2018-09-08 10:20:45'; get_time($start_time,$end_ti ...
- 《linux设备驱动开发详解》笔记——8阻塞与非阻塞IO
8.1 阻塞与非阻塞IO 8.1.0 概述 阻塞:访问设备时,若不能获取资源,则进程挂起,进入睡眠状态:也就是进入等待队列 非阻塞:不能获取资源时,不睡眠,要么退出.要么一直查询:直接退出且无资源时, ...
- Altium Designer入门学习笔记3:关于各模块分开布线的理解( 1)
观看"杜洋AD的讲解视频",杜洋着重强调了"模块分开"布线的好处. ---------------------------------------------- ...
- 带权并查集:CF-2015 ACM Arabella Collegiate Programming Contest(F题)
F. Palindrome Problem Description A string is palindrome if it can be read the same way in either di ...
- 数据挖掘算法——Apriori
在上一篇数据挖掘入门算法整理中提到,Apriori算法是关联规则算法中使用最为广泛的算法,这次我们就来学习下该算法的基本知识. 一.算法概述 Apriori 算法是一种最有影响力的挖掘布尔关联规则的频 ...
- 反转单词顺序 VS 左旋转字符串
题目一:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变.为简单起见,标垫符号和普通字母一样处理.例如输入字符串“I am a student.”,则输出“student. a am I ...
- 光学字符识别OCR-3
连通性 可以看到,每一层的图像是由若干连通区域组成的,文字本身是由笔画较为密集组成的,因此往往文字也能够组成一个连通区域.这里的连通定义为 8邻接,即某个像素周围的8个像素都定义为邻接像素,邻接的像素 ...