1_JDK版本
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
2_固定值成员变量
-
DEFAULT_INITIAL_CAPACITY
-
固定值=1 << 4 = 16
-
默认的初始容量-必须是2的幂。
-
-
MAXIMUM_CAPACITY
- 固定值=1 << 30 = 2^30
- 最大容量,如果更高的值由任何一个构造函数用参数隐式指定,则使用该值。必须是二的幂<= 1<<30。
-
DEFAULT_LOAD_FACTOR
- 固定值=0.75f
- 在构造函数中没有指定时使用的负载因子。负载因子用于HashMap扩容的逻辑
-
TREEIFY_THRESHOLD
- 固定值=8
- 对于bin使用树而不是列表时的bin计数阈值。当向至少有这么多节点的bin中添加元素时,bin将转换为树。该值必须大于2,并且应该至少为8,以便与树移除中关于收缩时转换回普通容器的假设相匹配。
- TREEIFY_THRESHOLD用于链表转红黑树的逻辑
-
UNTREEIFY_THRESHOLD
- 固定值=6
- 在调整大小操作期间取消树化(分割)料仓的料仓数阈值。应小于TREEIFY_THRESHOLD,且最多为6以去除收缩检测相匹配。
- UNTREEIFY_THRESHOLD用于红黑树转链表的逻辑
-
MIN_TREEIFY_CAPACITY
- 固定值=64
- 可以对容器进行树化的最小表容量。(否则,如果一个bin中有太多的节点,则会调整表的大小。)应该至少是4 * TREEIFY_THRESHOLD,以避免调整大小和树化阈值之间的冲突。
- MIN_TREEIFY_CAPACITY用于链表转红黑树的逻辑
3_内部类
Node是HasMap存储键值对的具体类型, 除了key, value, Node节点还额外的定义了hash值, 和next节点用于解决Hash冲突存储链表结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
3.1_成员变量
- final int hash key的hash值
- final K key key
- V value vaue
- Node<K,V> 当前节点的Next节点
4_静态方法
4.1_hash
static final int hash(Object key) {
int h;
// 异或 = 相同为0 不同为1
// key本身的hashCode()高16位和低16位坐异或操作, 高16位与0异或(高16位相当于不变)
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
定义此方法主要是防止对象的hashCode()方法设计太差, 导致数据大面积hash碰撞
4.2_comparableClassFor
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
if ((c = x.getClass()) == String.class) // bypass checks
return c;
if ((ts = c.getGenericInterfaces()) != null) {
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
只有以下情况才不会返回null
public class User implements Comparable<User> {
@Override
public int compareTo(User o) {
return 0;
}
}
System.out.println(comparableClassFor(new User()));
结果为
class top.yuhaitao.main.User
4.3_tableSizeFor
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
该方法的作用是找到大于等于某个数字的最小的2^n数
1=2, 2=2, 3=4, 7=8, 8=8
5_成员变量
5.1_table
transient Node<K,V>[] table;
源码注释:
表,在第一次使用时初始化,并根据需要调整大小。分配时,长度总是2的幂。(我们还允许某些操作的长度为0,以允许当前不需要的引导机制。
自译: HashMap内部的数组结构
5.2_entrySet
transient Set<Map.Entry<K,V>> entrySet;
源码注释: 保存缓存entrySet()。注意,AbstractMap字段用于keySet()和values()。
将Map的所有节点转换成entrySet, 用于缓存
5.3_size
transient int size;
源码注释: 此映射中包含的键-值映射的数量。即当前Map的大小
5.4_modCount
源码注释: 此 HashMap 已被结构修改的次数 结构修改是指更改 HashMap 中的映射数量或以其他方式修改其内部结构(例如,重新散列)的那些。该字段用于使 HashMap 的 Collection-views 上的迭代器快速失败。 (请参阅 ConcurrentModificationException)。
5.5_loadFactor
final float loadFactor
源码注释: 哈希表的负载因子。
扩容阈值 = size() * loadFactor, 当插入元素大于阈值的时候才会扩容
注意: 是大于才扩容, 等于阈值是不扩容的
6_构造函数
6.1_无参构造
- 无参构造函数
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
- 调用构造函数
Map<String, String> map = new HashMap<>();
- 调用后HashMap的内部线程栈状态为
- threshold = 0
- entrySet = null
- table = null
- size = 0
- loadFactor = 0.75
- table = null
- size = 0
- this = {HashMap@897} size = 0
可以看到无参构造方法在Jdk1.8以后, 除了赋值一个loadFactor=0.75以后, 没有做任何数据处理, 并没有初始化table数组, 猜测是为了提升性能, 因为很多场景会new一个HashMap, 但是没有添加数据, 比如接口校验入参错误返回一个空集合
6.2_有参构造
- 构造函数
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
可以看到有参构造有两个
一个是 带初始化容量的
一个是 带初始化容量并且可以设置负载因子的
- tableSizeFor()
始化容量并非设置了多少, HashMap的容量就是多少, HashMap会调用tableSizeFor()方法找到大于等于入参的最小的2^n数
- 传入1实际容量为2
- 传入2实际容量为2
- 传入3实际容量为4
- 传入7实际容量为8
- 传入8实际容量为8
- 调用后HashMap(32)的内部线程栈状态为
- threshold = 32
- entrySet = null
- table = null
- size = 0
- loadFactor = 0.75
- table = null
- size = 0
调用了无参的构造之后, 除了threshold, loadFactor 其他参数都没有初始化
7_PUT
带着几个问题去看PUT源码
- 当前元素放在HashMap的具体位置, 是数组的那个节点, 还是链接的那个节点, 异或红黑数的那一个节点?
- 存放元素的Hash冲突了怎么办?
- 什么时候扩容, 小于阈值的一个数扩容, 还是等于阈值扩容, 还是大于阈值扩容?
- 扩容的流程是怎样的一个过程?
- 什么时候会产生链表
- 什么时候产生红黑树
- 源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
7.1_分支判断1
if ((tab = table) == null || (n = tab.length) == 0)
// 当使用了new HashMp(), 第一次put元素会走这一段代码
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
当我们调用无参的构造方法时, table = null命中该条件, 发现改方法调用了扩容方法, 扩容方法请看下文
- 扩容后的线程栈状态为
- threshold = 12
- entrySet = null
- table = {HashMap$Node[16]@461}
- size = 1
- loadFactor = 0.75
- table = {HashMap$Node[16]@461}
- size = 1
- this = {HashMap@460} size = 1
- Variables debug info not available
- hash = 49
- key = “1”
- value = “11”
- onlyIfAbsent = false
- evict = true
- tab (slot_6) = {HashMap$Node[16]@461}
- p (slot_7) = null
- n (slot_8) = 16
- i (slot_9) = 1
- slot_10 = null
- slot_11 = null
- slot_12 = null
发现HashMap数组table初始化为长16的数组, 阈值threshold=table.length * loadFactor=12
7.2_分支判断2
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
这里可以看到元素存储的位置算法为HashMap数组table的(长度-1)与上hash(key), 因为与上数组的长度减去1, 所以最终落到了数组的某一个节点上面
tab[i] = newNode(hash, key, value, null);
Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
return new Node<>(hash, key, value, next);
}
因为第一次添加元素, 所以不可能产生hash碰撞, 存储的元素最终会构建成HashMap内部的类Node进行存储
7.3_分支判断3
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
分支判断3下面又延伸出3个分支
7.3.1_分支判断3.1
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
首先回顾一下 p = tab[i = (n - 1) & hash], p表示put元素命中数组的那个节点元素
判断了三个条件
- 数组节点的hash=要放入元素key的hash
- 数组节点的key索引=要放入元素key (索引相等)
- 数组节点的key要equals要放入元素key
以上三个条件充分必要的说明了, 要放入的元素就key, 跟之前存放的元素key完全相等
e = p;
发现, 如果要存入的元素的key值完全等于之前元素的key值, 那么直接将原来替换为新的值
7.3.2_分支判断3.2
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
判断当前节点是TreeNode, 那么执行放入红黑树的逻辑
TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
---
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
发现HashMap.TreeNode继承LinkedHashMap.Entry, 然后LinkedHashMap.Entry继承HashMap.Node<K,V>, TreeNode就是HashMap的Entry的一个实现
红黑树代码具体就不展开了, 可以自行参考HashMap内部类TreeNode
分支判断3.3
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
for (int binCount = 0; ; ++binCount) {
表示一个死循环
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
如果当前节点的下一个节点为null, 那么直接将要存入的节点赋值给当前的节点的next
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
判断当前链接表长度是否>=8,>=8执行转换红黑树的逻辑
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
来看下转红黑树的代码, 第一句代码
// MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
如果hashMap内部的数组不是null, 并且HashMap内部的数组(即HashMap的容量)<64的话, 直接扩容, 否则就把链表转换成红黑树(此处不在展开)
那么得出结论, 要使数组下挂的链表变成红黑树的话有两个条件
- 数组下挂的链表长度要>=8
- HashMap的容量>=64
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
做了一个重复条件判断, 判断条件同分支1
p = e, 进行链表下挂
7.4_扩容逻辑
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
++size > threshold 的时候扩容
如果默认大小为16的HashMap, 那么阈值threshold=16*0.75=12, 到底是存放第11, 还是第12, 还是第13个元素的时候进行扩容的呢?
当存放第11个元素代码执行值线程栈的值为
- threshold = 12
- entrySet = null
- table = {HashMap$Node[16]@550}
- size = 10
- loadFactor = 0.75
- table = {HashMap$Node[16]@550}
- size = 10
++size > threshold => 11>12不成立, 没有执行扩容
当存放第12个元素代码执行值线程栈的值为
- threshold = 12
- entrySet = {HashMap$EntrySet@553} size = 11
- table = {HashMap$Node[16]@550}
- size = 11
- loadFactor = 0.75
- table = {HashMap$Node[16]@550}
- size = 11
++size > threshold => 12>12不成立, 没有执行扩容
当存放第13个元素代码执行值线程栈的值为
- threshold = 12
- entrySet = {HashMap$EntrySet@553} size = 12
- table = {HashMap$Node[16]@550}
- size = 12
- loadFactor = 0.75
- table = {HashMap$Node[16]@550}
- size = 12
++size > threshold => 13>12成立, 进行了扩容
当存放第14个元素代码执行值线程栈的值为
- threshold = 24
- entrySet = null
- table = {HashMap$Node[32]@549}
- size = 13
- loadFactor = 0.75
- table = {HashMap$Node[32]@549}
- size = 13
- this = {HashMap@547} size = 13
++size > threshold => 14>24不成立, 没有执行扩容
答案: 是在存入13个元素的时候进行扩容, 存入14个元素的时候已经扩容过了
所以是大于阈值的时候进行扩容
7.5_问题解答
- 当前元素放在HashMap的具体位置, 是数组的那个节点, 还是链接的那个节点, 异或红黑数的那一个节点?
- 当前key的hash值, 这个hash其实是经过了HashMap的转换, 与上当前HashMap内部数组的长度-1, 最后算出存放在数组的位置
- 存放元素的Key的Hash冲突了怎么办?
- 冲突了判断当前key的hash值, key的内存索引值, key的实际值, 如果相等替换当前元素,
- 并且生成了链表
- 当链表的长度>=8, 并且HashMap总容量>=64, 将链表替换成红黑树
- 什么时候扩容, 小于阈值的一个数扩容, 还是等于阈值扩容, 还是大于阈值扩容?
- 大于阈值进行扩容操作
- 扩容的流程是怎样的一个过程?
- 请看后面的扩容代码
- 什么时候会产生链表
- 存放元素的Key的Hash冲突, 会进行链表下挂, 当链表的长度>=8, 并且HashMap总容量>=64, 将链表替换成红黑树
- 什么时候产生红黑树
- 存放元素的Key的Hash冲突, 会进行链表下挂, 当链表的长度>=8, 并且HashMap总容量>=64, 将链表替换成红黑树
8_resize
源码如下
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
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 threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (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"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.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;
}
8.1_第一次运行进行初始化扩容
8.1.1_如果使用了new HasMap()的空参数构造方法
初始值为
- loadFactor = 0.75
- size = 0
- table = null
- entrySet = null
- threshold = 0
最终会走到以下这段代码
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
执行完毕之后
- threshold = 12
- entrySet = {HashMap$EntrySet@946} size = 0
- table = {HashMap$Node[16]@948}
- size = 0
- loadFactor = 0.75
- table = {HashMap$Node[16]@948}
- size = 0
- this = {HashMap@940} size = 0
- Variables debug info not available
- oldTab (slot_1) = null
- oldCap (slot_2) = 0
- oldThr (slot_3) = 0
- newCap (slot_4) = 16
- newThr (slot_5) = 12
- newTab (slot_6) = {HashMap$Node[16]@948}
最终生成一个容量为16的HashMap(table)
8.1.2_如果使用了new HasMap(32)的带容量参数构造方法
初始值为
- loadFactor = 0.75
- size = 0
- table = null
- entrySet = {HashMap$EntrySet@967} size = 0
- threshold = 32
最终会走到以下这段代码
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
运行后各个线程栈为
- loadFactor = 0.75
- size = 0
- table = {HashMap$Node[32]@984}
- entrySet = {HashMap$EntrySet@967} size = 0
- threshold = 24
- this = {HashMap@960} size = 0
- Variables debug info not available
- oldTab (slot_1) = null
- oldCap (slot_2) = 0
- oldThr (slot_3) = 32
- newCap (slot_4) = 32
- newThr (slot_5) = 24
- newTab (slot_6) = {HashMap$Node[32]@984}
最终生成一个容量为32的HashMap(table)
8.2_当大于阈值扩容的逻辑
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
- 常规情况oldCap=16没有大于最大的容量, 发现新的容量是旧的容量左移一位, 其实就是扩大一倍
- oldCap >= DEFAULT_INITIAL_CAPACITY, oldCap>=16的时候, 阈值也会扩大一倍
运行后各个线程栈为
- oldCap (slot_2) = 16
- oldThr (slot_3) = 12
- newCap (slot_4) = 32
- newThr (slot_5) = 24
另一个如果oldCap制定为8, 阈值还会扩容吗? 答案是会的, 如果制定hashMap(8)
下面还有一段补救的代码
int newCap, newThr = 0;
...
if (newThr == 0) {
ft = 16 * 0.75
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
运行后各个线程栈为
- oldCap (slot_2) = 16
- oldThr (slot_3) = 12
- newCap (slot_4) = 32
- newThr (slot_5) = 24
对象迁移的代码
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.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;
}
}
}
}
}
8.2.1_当前节点非红黑树非链表
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
循环数组的每一个节点 , e.next == null表示原来的节点非链表, 也非红黑树, 那么直接讲原来的位置置为null, 元素的hash与上新的数组长度-1,计算出存放的位置
8.2.2_当前节点是一颗红黑树
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
...
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
数据从旧数组转移到新数组上来時,旧数组上的数据会根据(e.hash & oldCap) 是否等于0,重新rehash计算其在新数组上的索引位置,分成2类:
① 等于0时,则将该树链表头节点放到新数组时的索引位置等于其在旧数组时的索引位置,记未低位区树链表lo开头-low;
② 不等于0时,则将该树链表头节点放到新数组时的索引位置等于其在旧数组时的索引位置再加上旧数组长度,记为高位区树链表hi开头high
当红黑树被split分割开成为两个小红黑树后:
① 当低位区小红黑树元素个数小于等于6时,开始去树化untreeify操作;
② 当低位区小红黑树元素个数大于6且高位区红黑树不为null时,开始树化操作(赋予红黑树的特性)
当红黑树被split分割开成为两个小红黑树后:
① 当高位区小红黑树元素个数小于等于6时,开始去树化untreeify操作;
② 当高位区小红黑树元素个数大于6且地位区红黑树不为null时,开始树化操作(赋予红黑树的特性)
8.2.3_当前节点是链表
链表处理过程和红黑树类似, 将链表拆分成两个小链表, 低位的链表直接下挂的原坐标, 高位下挂到, 原位置
8.2.4_思考HashMap需要保证容量比稀释2的N次幂, 原因就是在这里
-
对于存放位置算法
(p = tab[i = (n - 1) & hash])
, 要想使元素占满整个HashMap内部table数组的没一个节点, (n - 1)的二进制必须全部是1, 即2^n-1, 所以HashMap容量必须是2^n -
对于jdk1.8 初始容量必须是2^n, 其实是为了扩容铺路 在扩容的时候, 内部数组节点类型分为3类 1. 普通节点 2. 红黑树 3. 链表 其中红黑树和链表扩容的时候, 都会拆成2个红黑树或2个链接 低位的会直接迁移到跟原数组一样的位置, 高位的会迁移到原数组位置+原数组长度的位置 普通节点会迁移到hash&(扩容后长度-1)的节点 那么为了保证以上算法成立, 并且各种类型的节点不能相冲突导致丢失数据 必须要保证普通节点 hash&(扩容后长度-1) = 原数组位置 或者 原数组位置+原数组长度 所以容量必须是2^n才能成立 具体参考HashMap源码677行 java.util.HashMap#resize()
那么扩容的时候为啥不是遍历原HashMap, 一个一个往新的HashMap进行put()操作呢? 那效率简直是太慢了!!!
TODO更新中…