mirror of
https://github.com/youthlql/JavaYouth.git
synced 2026-03-13 21:33:42 +08:00
纠错与更改所有文章的图床
This commit is contained in:
@@ -8,9 +8,9 @@ categories:
|
||||
- HashMap
|
||||
keywords: Java集合,HashMap。
|
||||
description: HashMap-JDK7源码讲解。
|
||||
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_Basis/logo.png'
|
||||
top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg'
|
||||
date: 2020-11-1 10:21:58
|
||||
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_Basis/logo.png'
|
||||
abbrlink: f1f58db2
|
||||
date: 2020-11-01 10:21:58
|
||||
---
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ hadoop2
|
||||
|
||||
大致是这样的一个结构
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0001.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0001.png">
|
||||
|
||||
- 每个链表就算哈希表的桶(bucket)
|
||||
- 链表的节点值就算一个键值对
|
||||
@@ -294,7 +294,8 @@ public class HashMap<K,V>
|
||||
|
||||
|
||||
/*
|
||||
1、扩容阈值(threshold):当哈希表的大小【就是上面的size】 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的 容量)
|
||||
1、扩容阈值(threshold):当哈希表的大小【就是上面的size】 ≥ 扩容阈值时,就会扩容哈希表
|
||||
(即扩充HashMap的容量)
|
||||
2、扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
|
||||
3、扩容阈值 = 容量 x 加载因子
|
||||
*/
|
||||
@@ -397,7 +398,7 @@ static class Entry<K,V> implements Map.Entry<K,V> {
|
||||
|
||||
`HashMap`中的数组元素 & 链表节点 采用 `Entry`类实现
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0001.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0001.png">
|
||||
|
||||
1、一个正方形代表一个Entry对象,同时也代表一个键值对。
|
||||
|
||||
@@ -456,8 +457,10 @@ static class Entry<K,V> implements Map.Entry<K,V> {
|
||||
|
||||
/*
|
||||
设置扩容阈值 = 初始容量
|
||||
1、注意:此处不是真正的阈值,仅是为了接收参数初始容量大小(capacity)、加载因子(Load factor),并没 有真正初始化哈希表,即初始化存储数组table
|
||||
2、真正初始化哈希表(初始化存储数组table)是在第1次添加键值对时,即第1次调用put()时,下面会详细说明。
|
||||
1、注意:此处不是真正的阈值,仅是为了接收参数初始容量大小(capacity)、加载因子(Load factor),
|
||||
并没有真正初始化哈希表,即初始化存储数组table
|
||||
2、真正初始化哈希表(初始化存储数组table)是在第1次添加键值对时,即第1次调用put()时,下面会
|
||||
详细说明。
|
||||
*/
|
||||
threshold = initialCapacity;
|
||||
|
||||
@@ -495,14 +498,17 @@ static class Entry<K,V> implements Map.Entry<K,V> {
|
||||
public V put(K key, V value) {
|
||||
|
||||
/* ①
|
||||
1、若哈希表未初始化(即 table为空),则调用inflateTable方法,使用构造函数时设置的阈值(即初始容量)初 始化数组table
|
||||
1、若哈希表未初始化(即 table为空),则调用inflateTable方法,使用构造函数时设置的阈值
|
||||
(即初始容量)初始化数组table
|
||||
*/
|
||||
if (table == EMPTY_TABLE) {
|
||||
inflateTable(threshold);
|
||||
}
|
||||
/* ②
|
||||
1、判断key是否为空值null
|
||||
2、若key == null,则调用putForNullKey方法,putForNullKey方法最终将该键-值存放到数组table中的第1 个位置,即table[0]。本质:key = Null时,hash值 = 0,故存放到table[0]中)该位置永远只有1个value, 新传进来的value会覆盖旧的value
|
||||
2、若key == null,则调用putForNullKey方法,putForNullKey方法最终将该键-值存放到数组
|
||||
table中的第1个位置,即table[0]。本质:key = Null时,hash值 = 0,故存放到table[0]中)
|
||||
该位置永远只有1个value,新传进来的value会覆盖旧的value
|
||||
3、k != null往下走
|
||||
*/
|
||||
if (key == null)
|
||||
@@ -515,12 +521,14 @@ public V put(K key, V value) {
|
||||
int i = indexFor(hash, table.length);
|
||||
|
||||
/* ③
|
||||
1、通过遍历以该数组元素为头结点的链表,逐个判断是否发生hash冲突,同时判断该key对应的值是否已存在
|
||||
1、通过遍历以该数组元素为头结点的链表,逐个判断是否发生hash冲突,同时判断该key对应的值是
|
||||
否已存在
|
||||
*/
|
||||
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
|
||||
Object k;
|
||||
/* ④
|
||||
1、如果发生了hash冲突,且key也相等。则用新value替换旧value(此时说明发生了更新的情况),注意这里 强调的是发生了hash冲突并且key也相等。
|
||||
1、如果发生了hash冲突,且key也相等。则用新value替换旧value(此时说明发生了更新的情况),
|
||||
注意这里强调的是发生了hash冲突并且key也相等。
|
||||
*/
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
|
||||
V oldValue = e.value;
|
||||
@@ -550,14 +558,16 @@ public V put(K key, V value) {
|
||||
|
||||
private void inflateTable(int toSize) {
|
||||
/*
|
||||
将传入的容量大小转化为:>传入容量大小的最小的2的次幂,即如果传入的是容量大小是18,那么转化后,初始化容量 大小为32(即2的5次幂)
|
||||
将传入的容量大小转化为:>传入容量大小的最小的2的次幂,即如果传入的是容量大小是18,那么转化后,
|
||||
初始化容量大小为32(即2的5次幂)
|
||||
*/
|
||||
int capacity = roundUpToPowerOf2(toSize);
|
||||
|
||||
//重新计算阈值 threshold = 容量 * 加载因子
|
||||
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
|
||||
/*
|
||||
使用计算后的初始容量(已经是2的次幂) 初始化数组table(作为数组长度)即 哈希表的容量大小 = 数组大小(长 度)
|
||||
使用计算后的初始容量(已经是2的次幂) 初始化数组table(作为数组长度)即 哈希表的容量大小 =
|
||||
数组大小(长度)
|
||||
*/
|
||||
table = new Entry[capacity];
|
||||
initHashSeedAsNeeded(capacity);
|
||||
@@ -646,7 +656,8 @@ private V putForNullKey(V value) {
|
||||
## indexFor()
|
||||
|
||||
```java
|
||||
//这里h & (length-1)的意思就是hash值与数组长度取模。只是因为数组长度是特殊的2的幂,所以这个等价关系刚好成立
|
||||
//这里h & (length-1)的意思就是hash值与数组长度取模。只是因为数组长度是特殊的2的幂,
|
||||
//所以这个等价关系刚好成立
|
||||
static int indexFor(int h, int length) {
|
||||
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
|
||||
return h & (length-1);
|
||||
@@ -758,15 +769,15 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
大概画了一下图:
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0002.png"/>
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0002.png"/>
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0003.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0003.png">
|
||||
|
||||
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0004.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0004.png">
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0005.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0005.png">
|
||||
|
||||
|
||||
|
||||
@@ -860,7 +871,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
**hashmap初始状态**
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0006.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0006.png">
|
||||
|
||||
|
||||
|
||||
@@ -886,7 +897,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
**两个线程调用完毕之后,hashmap目前是这样的。**
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0007.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0007.png">
|
||||
|
||||
|
||||
|
||||
@@ -907,9 +918,9 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
3、来看下此时内存里的状态
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0008.png"/>
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0008.png"/>
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0009.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0009.png">
|
||||
|
||||
## 步骤4
|
||||
|
||||
@@ -946,7 +957,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
2、线程2直接**扩容完毕**,那么完成后的状态是这样【假设e2和e3还是hash到同一个位置】
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0010.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0010.png">
|
||||
|
||||
3、线程1还是原来的状态
|
||||
|
||||
@@ -956,11 +967,11 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
目前两个线程里的新数组是这样的
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0011.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0011.png">
|
||||
|
||||
为了方便后面观看,我画成这样。
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0012.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0012.png">
|
||||
|
||||
|
||||
|
||||
@@ -1004,7 +1015,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
也就变成了下面这个样子。
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0013.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0013.png">
|
||||
|
||||
|
||||
|
||||
@@ -1050,7 +1061,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
执行完,变成这样。
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0014.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0014.png">
|
||||
|
||||
|
||||
|
||||
@@ -1066,7 +1077,7 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
|
||||
3、执行pos_3: newTable[i] = e得到 newTable1[3] == e2
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK7/0015.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK7/0015.png">
|
||||
|
||||
这样就形成了循环链表,再get()数据就会陷入死循环。
|
||||
|
||||
@@ -1118,7 +1129,8 @@ void transfer(Entry[] newTable, boolean rehash) {
|
||||
//根据key值,通过hash()计算出对应的hash值
|
||||
int hash = (key == null) ? 0 : hash(key);
|
||||
|
||||
//根据hash值计算出对应的数组下标,遍历以该数组下标的数组元素为头结点的链表所有节点,寻找该key对应的值
|
||||
//根据hash值计算出对应的数组下标,遍历以该数组下标的数组元素为头结点的链表所有节点,
|
||||
//寻找该key对应的值
|
||||
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
|
||||
|
||||
Object k;
|
||||
|
||||
@@ -8,9 +8,9 @@ categories:
|
||||
- HashMap
|
||||
keywords: Java集合,HashMap。
|
||||
description: HashMap-JDK8源码讲解及常见面试题。
|
||||
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_Basis/logo.png'
|
||||
top_img: 'https://cdn.jsdelivr.net/gh/youthlql/lql_img/blog/top_img.jpg'
|
||||
date: 2020-11-1 10:22:05
|
||||
cover: 'https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_Basis/logo.png'
|
||||
abbrlink: cbc5672a
|
||||
date: 2020-11-01 10:22:05
|
||||
---
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ date: 2020-11-1 10:22:05
|
||||
|
||||
在JDK8中,优化了HashMap的数据结构,引入了红黑树。即HashMap的数据结构:数组+链表+红黑树。HashMap变成了这样。
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK8/0001.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK8/0001.png">
|
||||
|
||||
### 为什么要引入红黑树
|
||||
|
||||
@@ -146,16 +146,22 @@ date: 2020-11-1 10:22:05
|
||||
|
||||
//与红黑树相关的参数
|
||||
|
||||
//单链表(桶)的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,则将链表转换成红黑树
|
||||
/*
|
||||
1、单链表(桶)的树化阈值:即 链表转成红黑树的阈值,在存储数据时,当链表长度 > 该值时,
|
||||
则将链表转换成红黑树
|
||||
*/
|
||||
static final int TREEIFY_THRESHOLD = 8;
|
||||
|
||||
/*
|
||||
1、桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据存储位置会重新计 算),在重新计算存储位置后,当原有的红黑树内节点数量 < 6时,则将 红黑树转换成链表
|
||||
1、桶的链表还原阈值:即 红黑树转为链表的阈值,当在扩容(resize())时(此时HashMap的数据
|
||||
存储位置会重新计算),在重新计算存储位置后,当原有的红黑树内节点数量 < 6时,则将 红黑树转换
|
||||
成链表
|
||||
*/
|
||||
static final int UNTREEIFY_THRESHOLD = 6;
|
||||
|
||||
/*
|
||||
1、最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)。否则,若 (单链表)桶内元素太多时,则直接扩容,而不是树形化。
|
||||
1、最小树形化容量阈值:即 当哈希表中的容量 > 该值时,才允许树形化链表 (即 将链表 转换成红黑树)。
|
||||
否则,若 (单链表)桶内元素太多时,则直接扩容,而不是树形化。
|
||||
2、为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD
|
||||
*/
|
||||
static final int MIN_TREEIFY_CAPACITY = 64;
|
||||
@@ -205,8 +211,12 @@ public class HashMap<K,V>
|
||||
// 设置加载因子
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
// 设置扩容阈值
|
||||
// 此处不是真正的阈值,仅仅只是将传入的容量大小转化为:>传入容量大小的最小的2的幂,该阈值后面会重新计算
|
||||
|
||||
/*
|
||||
1、设置扩容阈值
|
||||
2、此处不是真正的阈值,仅仅只是将传入的容量大小转化为:>传入容量大小的最小的2的幂,
|
||||
该阈值后面会重新计算
|
||||
*/
|
||||
this.threshold = tableSizeFor(initialCapacity);
|
||||
|
||||
}
|
||||
@@ -342,7 +352,8 @@ Process finished with exit code 0
|
||||
|
||||
|
||||
/*
|
||||
1、若哈希表的数组tab为空,则通过resize()进行初始化,所以,初始化哈希表的时机就是第1次调用put函数时, 即调用resize() 初始化创建。
|
||||
1、若哈希表的数组tab为空,则通过resize()进行初始化,所以,初始化哈希表的时机就是第1次
|
||||
调用put函数时,即调用resize() 初始化创建。
|
||||
*/
|
||||
if ((tab = table) == null || (n = tab.length) == 0)
|
||||
n = (tab = resize()).length;
|
||||
@@ -358,12 +369,13 @@ Process finished with exit code 0
|
||||
|
||||
else {
|
||||
Node<K,V> e; K k;
|
||||
//判断 table[i]的元素的key是否与需插入的key一样,若相同则直接用新value覆盖旧value【即更新操作】
|
||||
//判断 table[i]的元素的key是否与需插入的key一样,若相同则直接用新value覆盖旧value
|
||||
//【即更新操作】
|
||||
if (p.hash == hash &&
|
||||
((k = p.key) == key || (key != null && key.equals(k))))
|
||||
e = p;
|
||||
|
||||
//继续判断:需插入的数据结构是否为红黑树 or 链表。若是红黑树,则直接在树中插入 or 更新键值对
|
||||
//继续判断:需插入的数据结构是否为红黑树or链表。若是红黑树,则直接在树中插入or更新键值对
|
||||
else if (p instanceof TreeNode)
|
||||
/*
|
||||
1、putTreeVal作用:向红黑树插入 or 更新数据(键值对)
|
||||
@@ -377,9 +389,10 @@ Process finished with exit code 0
|
||||
else {
|
||||
/*
|
||||
过程:
|
||||
1、遍历table[i],判断Key是否已存在:采用equals()对比当前遍历节点的key 与 需插入数据的 key:若已存在,则直接用新value覆盖旧value
|
||||
2、遍历完毕后仍无发现上述情况,则直接在链表尾部插入数据(尾插法)
|
||||
3、新增节点后,需判断链表长度是否>8(8 = 桶的树化阈值):若是,则把链表转换为红黑树
|
||||
1、遍历table[i],判断Key是否已存在:采用equals()对比当前遍历节点的key 与
|
||||
需插入数据的key:若已存在,则直接用新value覆盖旧value
|
||||
2、遍历完毕后仍无发现上述情况,则直接在链表尾部插入数据(尾插法)
|
||||
3、新增节点后,需判断链表长度是否>8(8 = 桶的树化阈值):若是,则把链表转换为红黑树
|
||||
*/
|
||||
for (int binCount = 0; ; ++binCount) {
|
||||
//对于2情况的操作 尾插法插入尾部
|
||||
@@ -432,7 +445,8 @@ Process finished with exit code 0
|
||||
int h;
|
||||
/*
|
||||
1、当key = null时,hash值 = 0,所以HashMap的key可为null
|
||||
2、当key ≠ null时,则通过先计算出 key的 hashCode()(记为h),然后对哈希码进行扰动处理。高位参与 低位的运算:h ^ (h >>> 16)
|
||||
2、当key ≠ null时,则通过先计算出 key的 hashCode()(记为h),然后对哈希码进行扰动处理。
|
||||
高位参与低位的运算:h ^ (h >>> 16)
|
||||
*/
|
||||
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
|
||||
|
||||
@@ -442,7 +456,7 @@ Process finished with exit code 0
|
||||
|
||||
JDK8 hash的运算原理:高位参与低位运算,使得hash更加均匀。
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK8/0002.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK8/0002.png">
|
||||
|
||||
|
||||
|
||||
@@ -555,7 +569,7 @@ JDK8 hash的运算原理:高位参与低位运算,使得hash更加均匀。
|
||||
|
||||
JDK8扩容时,数据在数组下标的计算方式
|
||||
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lql_img/Java_collection/HashMap/JDK8/0003.png">
|
||||
<img src="https://cdn.jsdelivr.net/gh/youthlql/lqlp@v1.0.0/Java_collection/HashMap/JDK8/0003.png">
|
||||
|
||||
* `JDK8`根据此结论作出的新元素存储位置计算规则非常简单,提高了扩容效率。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user