仔细瞄一下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 ...
随机推荐
- JavaScript -- 条件语句和循环语句
if语句 在我们开发程序的时候,经常会遇到选择题,例如,年龄大于18,你就可以抽烟喝酒烫头,年龄小于18,你就只能吃饭喝水.在我们的代码中,我们可以用if语句来实现这种判断 语法一: if( cond ...
- c++作业:使用函数调用的方法,实现求两个整数中大的数的程序。
#include <iostream> using namespace std; int main(){ //从键盘接收两个整数,保存在变量num1和num2中 cout<<& ...
- 协议(Protocol)与委托代理(Delegate)
协议(Protocol)的作用: 1. 规范接口,用来定义一套公用的接口: 2. 约束或筛选对象. 代理(Delegate): 它本身是一种设计模式,委托一个对象<遵守协议>去做某件事情, ...
- 【STL学习笔记】一、STL体系
目录 1.标准库以header files形式呈现 2.namespce命名空间 3.STL与OO 4.STL六组件及其关系 5.STL组件例子 6.range-based for statement ...
- 【思维题】TCO14 Round 2C InverseRMQ
全网好像就只有劼和manchery写了博客的样子……:正解可能是最大流?但是仔细特判也能过 题目描述 RMQ问题即区间最值问题是一个有趣的问题. 在这个问题中,对于一个长度为 n 的排列,query( ...
- python入门:while循环里面True和False的作用,真和假
#!/usr/bin/env python # -*- coding:utf-8 -*- #while循环里面True和False的作用,真和假 """ n1等于真(Tr ...
- 分享几个简单的技巧让你的 vue.js 代码更优雅
1. watch 与 computed 的巧妙结合 一个简单的列表页面. 你可能会这么做: created(){ this.fetchData() }, watch: { keyword(){ thi ...
- CUB reduce errorinvalid configuration argument
解决CUB reduce errorinvalid configuration argument问题 在写TensorFlow代码时遇到报错 CUB reduce errorinvalid confi ...
- 安装openstack同步数据库时出错解决方法
错误提示:(2003, "Can't connect to MySQL server on 'controller' ([Errno -2] Name or service not know ...
- Linux优化总结
1)netstat (*****)查看网络状态lntup或an 1.[listening|-l] 2.[--numeric|-n] 3.[--tcp|-t] 4.[--udp|-u] 5[--prog ...