本文会通过HashMap中的put方法为入口,进行源码解读,文章较长,需要耐心阅读
说明
/** */
: 代表注释,区别于例子注释
egx:
:代表例子注释
解读前须知
源码主要关注4⼤点:
- 确定哈希桶数组索引的位置
HashMap.hash() ——> hashCode() - 插⼊数据
HashMap.put() - 扩容机制
resize() - 红⿊树 ⾮常复杂,⼤家⾃学吧
treeify()
解读是通过HashMap的put
方法为入口进行分析,其中eg注释的都是测试例子,具体测试代码如下:
@Test
public void testResize(){
HashMap hashMap = new HashMap();
// 下标0和1插入Node
hashMap.put(0, "a0"); // 00000000
hashMap.put(1, "a1"); // 00000001
// 下标0,横向插入多个Node形成链表
hashMap.put(16, "a16"); // 00010000
hashMap.put(32, "a32"); // 00100000
hashMap.put(48, "a48"); // 00110000
hashMap.put(64, "a64"); // 01000000
hashMap.put(80, "a80"); // 01010000
hashMap.put(96, "a96"); // 01100000
hashMap.put(112, "a112"); // 01110000
hashMap.put(128, "a128"); // 10000000
// hashMap.put(144, "a144"); // 10010000
}
源码解读
解读之前,需要先了解下几个字段作用
/**
* 默认的初始容量
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量,之后会看到很多次
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认的加载因子,构造函数可以指定
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 树化阈值,默认达到8个节点树化
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 被treeified的最小表容量,否则仅仅resize。
* TREEIFY_THRESHOLD至少为4,以避免大小调整和treeification阈值之间的冲突。
*/
static final int MIN_TREEIFY_CAPACITY = 64;
先来看下put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put方法很简单,但注意,这里有一个hash
的方法,进入hash方法
static final int hash(Object key) {
int h;
/**
* 按位异或运算(^):两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同为1
*
* 扰动函数:(h = key.hashCode()) ^ (h >>> 16)
* 将key的hashCOde一分为二
* 【高半区16位】数据位不变
* 【低半区16位】数据与高半区16位数据进行异或操作,以此来加大低位的随机性
* 【注意】如果key的哈希code小于等于16位,那么是没有任何影响的。只有大于16位才会触发扰动函数的执行效果
*
* case1:
* h=高16位(全是0) and 低16位(有1)
* h >>> 16 = 低16位全部消失,那么变成了32位(全是0)
* h ^ (h >>> 16) = 原样输出
* case2:
* h=高16位(有1) and 低16位(有1)
* h >>> 16 = 低16位全部消失,那么变成了高16位(全是0)and低16位(有1)
* h ^ (h >>> 16) = 不是原样输出 将原高16位于原低16位进行扰动。
*/
// eg1: 110100100110^000000000000=110100100110,由于k1的hashCode都是在低16位,所以原样返回3366,没有扰动效果
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里其实就是利用高16位+低16位进行一个扰动,使得获取比较hash时,高16位也能参与hash,具体可以来看一张图比较明了
图中可以看到,h>16位的,则高16位与低16位进行异或,达到扰动效果,如果h<=16位的,则保持原样,没有扰动效果
put方法中除了hash,主要还是调用putVal
进行实现,putVal方法如下
/**
* Implements Map.put and related methods.
*
* @param hash key的hash值
* @param key key值
* @param value value值
* @param onlyIfAbsent 如果是true,则不改变已存在的值
* @param evict 如果为false,则表为创建模式
* @return previous value, or null if none
*/
// eg1: hash=0 key=0 value="a0" onlyIfAbsent=false evict=true
// eg2: hash=1 key=1 value="a1" onlyIfAbsent=false evict=true
// eg3: hash=16 key=16 value="a16" onlyIfAbsent=false evict=true
// eg4: hash=32 key=32 value="a32" onlyIfAbsent=false evict=true
// eg5: 由于执行步骤与eg4相似,故略过。
// eg6: hash=128 key=128 value="a128" onlyIfAbsent=false evict=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;
// eg1: table=null
// eg2: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null)
// eg3: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
// eg4: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
// eg6: table是长度为16的Node数组,且table[1]=Node(1, 1, "a1", null) ... table[6]=Node(6, 6, "a6", null)
/** 如果table是空的,则默认初始化一个长度为16的Node数组 */
if ((tab = table) == null || (n = tab.length) == 0) {
n = (tab = resize()).length;
}
// eg1: i = (n-1)&hash = (16-1)&0 = 1111&0000 = 0000 = 0; 即:p=tab[0]=null
// eg2: i = (n-1)&hash = (16-1)&1 = 1111&0001 = 0001 = 1; 即:p=tab[1]=null
// eg3: i = (n-1)&hash = (16-1)&16 = 1111&10000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
// eg4: i = (n-1)&hash = (16-1)&32 = 1111&100000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
// eg6: i = (n-1)&hash = (16-1)&128 = 1111&10000000 = 0000 = 0; 即:p=tab[0]=Node(0, 0, "a0", null)
/** 如果计算后的i在tab中没有数据,则新增Node节点 */
if ((p = tab[i = (n - 1) & hash]) == null) {
// eg1: tab[0] = newNode(0, 0, "a0", null)
// eg2: tab[1] = newNode(1, 1, "a1", null)
tab[i] = newNode(hash, key, value, null);
}
/** 如果下标i计算后,在tab数组中已存在,则执行以下逻辑 */
else {
Node<K, V> e;
K k;
// eg3: p.hash==0, hash==16,所以返回false
// eg4: p.hash==0, hash==32,所以返回false
// eg6: p.hash==0, hash==128,所以返回false
/** 如果与已存在的Node是相同的key值 */
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
e = p;
}
// eg3: p instanceof Node,所以为false
// eg4: p instanceof Node,所以为false
// eg6: p instanceof Node,所以为false
/** 如果已存在的Node是树节点 */
else if (p instanceof TreeNode) {
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
}
/** 如果已存在的节点key不同且是普通节点,则循环遍历链式Node,并对比hash何key,如果都不相同,则将新的Node拼装到链表的末尾。如果相同则进行更新 */
else {
for (int binCount = 0; ; ++binCount) {
// eg3: p.next == null
// eg4-loop1: p.next == Node(16, 16, "a16", null) 不为空
// eg4-loop2: p.next == null
if ((e = p.next) == null) {
// eg3: p.next = newNode(16, 16, "a16", null);
// eg4-loop2: p.next == newNode(32, 32, "a32", null);
// eg6: p.next == newNode(128, 128, "a128", null);
p.next = newNode(hash, key, value, null);
// eg3: binCount == 0
// eg4-loop2: binCount == 1
/** 如果链表大于8个Node,那么变为红黑树 */
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
{
// eg6: tab={newNode(0, 0, "a0", [指向后面1个链表中的7个node]), newNode(1, 1, "a1", null)}, hash=128
/** 将链表变为红黑树 */
treeifyBin(tab, hash);
}
// eg3: break
// eg4-loop2: break
break;
}
// eg4-loop1: e.hash==16 hash==32 所以返回false
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
break;
}
// eg4-loop1: p=e=Node(16, 16, "a16", null)
p = e;
}
}
// eg3: e = null
// eg4: e = null
/** 以上的多个判断e不等于null,则表示存在相同的key值 */
if (e != null) { // existing mapping for key
// egx: String oldValue = "v1"
V oldValue = e.value;
// egx: onlyIfAbsent=false
if (!onlyIfAbsent || oldValue == null) {
// egx: e = Node(3366, "k1", "v2", null)
/** 则将Node(e)新的value值进行更新 */
e.value = value;
}
afterNodeAccess(e);
// egx: 返回oldValue="v1"
return oldValue;
}
}
// eg1: modCount==0 ++modCount==1
// eg2: modCount==1 ++modCount==2
// eg3: modCount==7 ++modCount==8
// eg4: modCount==8 ++modCount==9
++modCount;
// eg1: size=0, threshold=12
// eg2: size=1, threshold=12
// eg3: size=7, threshold=12
// eg4: size=8, threshold=12
/** size+1>threshold,说明需要扩容了 */
if (++size > threshold) {
resize();
}
afterNodeInsertion(evict);
return null;
}
这个方法包含了主要的逻辑,具体功能可以详细看注释,其中有两个地方执行了resize()
方法,一个是table为空的时候;一个是size达到了阈值,需要进行扩容,我们来看看reseze()方法
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
* <p>
* 【重要】table扩容
* <p>
* 常量名词解释
* Tab->Table 表
* Cap->Capacity 容量
* Thr->Threshold 阈值
*/
final Node<K, V>[] resize() {
// eg1: table=null
// eg6: table != null
Node<K, V>[] oldTab = table;
// eg1: oldCap=0
// eg6: oldCap=16
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// eg1: oldThr=threshold=0
// eg6: oldThr=threshold=12
int oldThr = threshold;
int newCap, newThr = 0;
/** 第一步:根据情况调整新表的容量newCap和阈值newThr */
if (oldCap > 0) {
/** 如果老的table的长度大于等于2^30(1 << 30) */
if (oldCap >= MAXIMUM_CAPACITY) {
/** threshold=2^31-1 */
threshold = Integer.MAX_VALUE;
return oldTab;
}
/** 如果将老的容量oldCap*2做为新的容量newCap后还是小于2^30并且老的容量大于16(1<<4) */
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
// eg6: newCap=32, newThr=24
/** newThr=oldThr*2 */
newThr = oldThr << 1;
}
}
// 初始容量设为阈值
else if (oldThr > 0) {
newCap = oldThr;
} else { // zero initial threshold signifies using defaults
// eg1: oldCap=0 newCap=16 newThr=0.75f*16=12
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);
}
// eg1: threshold=newThr=12
// eg6: threshold=newThr=24
threshold = newThr;
/** 第二步:根据newCap和newThr,构建新数组(初始化新表) */
@SuppressWarnings({"rawtypes", "unchecked"})
Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap];
// eg1: table=newTab=(Node<K, V>[]) new Node[16];
// eg6: table=newTab=(Node<K, V>[]) new Node[32];
table = newTab;
// eg1: oldTab=null
/** 如果老的oldTab里面有数据,则进行数据迁移到新的newTab */
if (oldTab != null) {
// eg6: oldCap=16
for (int j = 0; j < oldCap; ++j) {
Node<K, V> e;
// eg6-loop1: j=0, e=oldTab[0]=Node(0, 0, "a0", nodeRef)
// eg6-loop2: j=1, e=oldTab[1]=Node(1, 1, "a1", null)
if ((e = oldTab[j]) != null) {
// eg6-loop1: oldTab[0] = null
// eg6-loop2: oldTab[1] = null
/** 将j位置的Node置空 */
oldTab[j] = null;
// eg6-loop1: e.next=Node(16, 16, "a16", nodeRef)
// eg6-loop2: e.next==null
/** 如果没有后置节点,说明e是最后一个节点 */
if (e.next == null) {
// eg6-loop2: e.hash==1, newCap=32, 1&(32-1)==1 即:newTab[1]=Node(1, 1, "a1", null)
newTab[e.hash & (newCap - 1)] = e;
/** 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 {
// eg6-loop1-loop1: next=e.next=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop2: next=e.next=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop3: next=e.next=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop4: next=e.next=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop5: next=e.next=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop6: next=e.next=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop7: next=e.next=Node(112, 112, "a112", nodeRef)
// eg6-loop1-loop8: next=e.next=Node(128, 128, "a128", nodeRef)
// eg6-loop1-loop9: next=e.next=null
/** 获取oldTable数组下标的下一个节点 */
next = e.next;
// eg6-loop1-loop1: e.hash=0, oldCap=16, 00000000&10000==00000==0
// eg6-loop1-loop2: e.hash=16, oldCap=16, 00010000&10000==10000==16
// eg6-loop1-loop3: e.hash=32, oldCap=16, 00100000&10000==00000==0
// eg6-loop1-loop4: e.hash=48, oldCap=16, 00110000&10000==10000==16
// eg6-loop1-loop5: e.hash=64, oldCap=16, 01000000&10000==00000==0
// eg6-loop1-loop6: e.hash=80, oldCap=16, 01010000&10000==00000==16
// eg6-loop1-loop7: e.hash=96, oldCap=16, 01100000&10000==00000==0
// eg6-loop1-loop8: e.hash=112, oldCap=16, 01110000&10000==10000==16
// eg6-loop1-loop9: e.hash=128, oldCap=16, 10000000&10000==10000==0
/** 计算e在老表oldTable的下标,如果是第一个Node,即下标index==0 */
if ((e.hash & oldCap) == 0) {
if (loTail == null) {
// eg6-loop1-loop1: loHead=e=Node(0, 0, "a0", nodeRef)
/** 将loHead指向oldTable数组的第一个元素e */
loHead = e;
} else {
// eg6-loop1-loop3: loTail.next=e=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop5: loTail.next=e=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop7: loTail.next=e=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop9: loTail.next=e=Node(128, 128, "a128", nodeRef)
loTail.next = e;
}
// eg6-loop1-loop1: loTail=e=Node(0, 0, "a0", nodeRef)
// eg6-loop1-loop3: loTail=e=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop5: loTail=e=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop7: loTail=e=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop9: loTail=e=Node(128, 128, "a128", nodeRef)
/** 将loTail指向oldTable中的第一个元素e */
loTail = e;
} else {
if (hiTail == null) {
// eg6-loop1-loop2: hiHead=e=Node(16, 16, "a16", nodeRef)
hiHead = e;
} else {
// eg6-loop1-loop4: hiTail.next=e=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop6: hiTail.next=e=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop8: hiTail.next=e=Node(112, 112, "a112", nodeRef)
hiTail.next = e;
}
// eg6-loop1-loop2: hiTail=e=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop4: hiTail=e=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop6: hiTail=e=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop8: hiTail=e=Node(112, 112, "a112", nodeRef)
hiTail = e;
}
// eg6-loop1-loop1: e=next=Node(16, 16, "a16", nodeRef)
// eg6-loop1-loop2: e=next=Node(32, 32, "a32", nodeRef)
// eg6-loop1-loop3: e=next=Node(48, 48, "a48", nodeRef)
// eg6-loop1-loop4: e=next=Node(64, 64, "a64", nodeRef)
// eg6-loop1-loop5: e=next=Node(80, 80, "a80", nodeRef)
// eg6-loop1-loop6: e=next=Node(96, 96, "a96", nodeRef)
// eg6-loop1-loop7: e=next=Node(112, 112, "a112", nodeRef)
// eg6-loop1-loop8: e=next=Node(128, 128, "a128", nodeRef)
// eg6-loop1-loop9: e=next=null
} while ((e = next) != null);
// eg6-loop1: loTail=Node(128, 128, "a128", null)
if (loTail != null) {
loTail.next = null;
// eg6-loop1: j=0, newTab[0]=loHead=Node(0, 0, "a0", nodeRef)
newTab[j] = loHead;
}
// eg6-loop1: loTail=Node(112, 112, "a112", nodeRef)
if (hiTail != null) {
// eg6-loop1: loTail=Node(112, 112, "a112", null)
hiTail.next = null;
// eg6-loop1: j=0, oldCap=16, newTab[16]=hiHead=Node(16, 16, "a16", nodeRef)
newTab[j + oldCap] = hiHead;
}
}
}
}
}
// eg1: newTab = (Node<K, V>[]) new Node[16]
// eg6: newTab = (Node<K, V>[]) new Node[32]
return newTab;
}
这里主要有如下几种情况:
- 老的容量大于0,且老的容量大于2^30 ,则设置新的阈值为,2^31-1
- 老的容量大于0,且老的容量2大于2^30且老的容量大于默认初始化容量16,则新的阈值为老的容量2
- 老的阈值大于0,新容量=老的阈值大小
- 其它情况,新容量等于默认初始化容量16,新阈值等于
默认的加载因子(DEFAULT_LOAD_FACTOR)*默认初始化容量(DEFAULT_INITIAL_CAPACITY)
接下来就是对确定好的阈值和容量进行构建新表,遍历oldTab的时候,执行逻辑有如下几种情况:
- 如果是叶子节点,直接将e赋值给newTab
- 如果是树节点(TreeNode),调用
split
方法 - 其它情况,构建链表,并将表头赋值给newTab
这里有一个关键方法,就是split
,这个方法是在TreeNode私有类中,方法主要作用是将tree bins中的节点分成上下两个tree bins,如果tab太小就取消split,仅调整大小,我们来详细看下:
/**
* 将tree bins中的节点分成上下两个tree bins,如果tab太小就取消split,仅调整大小
*
* @param map the map
* @param tab the table for recording bin heads
* @param index the index of the table being split
* @param bit the bit of hash to split on
*/
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
// 做个赋值,因为这里是((TreeNode<K,V>)e)这个对象调用split()方法,所以this就是指(TreeNode<K,V>)e对象,所以才能类型对应赋值
TreeNode<K, V> b = this;
// 设置低位首节点和低位尾节点
TreeNode<K, V> loHead = null, loTail = null;
// 设置高位首节点和高位尾节点
TreeNode<K, V> hiHead = null, hiTail = null;
// 定义两个变量lc和hc,初始值为0,后面比较要用,他们的大小决定了红黑树是否要转回链表
int lc = 0, hc = 0;
// 这个for循环就是对从e节点开始对整个红黑树做遍历
for (TreeNode<K, V> e = b, next; e != null; e = next) {
// 取e的下一节点赋值给next遍历
next = (TreeNode<K, V>) e.next;
// 取好e的下一节点后,把它赋值为空,方便GC回收
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;
}
}
// 如果低位链表首节点不为null,说明有这个链表存在
if (loHead != null) {
// 如果链表下的元素小于等于6
if (lc <= UNTREEIFY_THRESHOLD) {
// 那就从红黑树转链表了,低位链表,迁移到新数组中下标不变,还是等于原数组到下标
tab[index] = loHead.untreeify(map);
} else {
// 低位链表,迁移到新数组中下标不变,还是等于原数组到下标,把低位链表整个拉到这个下标下,做个赋值
tab[index] = loHead;
// 如果高位首节点不为空,说明原来的红黑树已经被拆分成两个链表了
if (hiHead != null) {
//那么就需要构建新的红黑树了
loHead.treeify(tab);
}
}
}
// 如果高位链表首节点不为null,说明有这个链表存在
if (hiHead != null) {
// 如果链表下的元素小于等于6
if (hc <= UNTREEIFY_THRESHOLD) {
// 那就从红黑树转链表了,高位链表,迁移到新数组中的下标=【旧数组+旧数组长度】
tab[index + bit] = hiHead.untreeify(map);
} else {
// 高位链表,迁移到新数组中的下标=【旧数组+旧数组长度】,把高位链表整个拉到这个新下标下,做赋值
tab[index + bit] = hiHead;
// 如果低位首节点不为空,说明原来的红黑树已经被拆分成两个链表了
if (loHead != null) {
// 那么就需要构建新的红黑树了
hiHead.treeify(tab);
}
}
}
}
就是将一个链表拆分为两个链表,完成拆分后判断每个链表的元素个数,情况如下:
- 个数小于等于6(UNTREEIFY_THRESHOLD),则将红黑树转链表
- 个数大于6,且头节点不为空,则将链表转红黑树
为了便于大家理解,我们通过一张图来理解以上的split的方法:
这个方法中有涉及到红黑树转换,我们来了解下treeifyBin
方法:
在之前putVal方法中,也有进行转红黑树的操作,调用方法为treeifyBin
,和treeify
的区别主要是增加了条件判断,比如表长度是否满足最小数容量的要求,如果不是仅仅做扩容操作resize,我们来看看具体实现:
/**
* 依据给定的hash替换此节点为树形节点,并树形化。如果表太小则是调整容量resize。
*/
// eg6: hash=128
// tab[0]=Node(0, 0, "a0", nodeRef)——>Node(16, 16, "a16", nodeRef)——>Node(32, 32, "a32", nodeRef)——>Node(48, 48, "a48",nodeRef)——>Node(64, 64, "a64", node)——>Node(80, 80, "a80", node)——>Node(96, 96, "a96", node)——>Node(112, 112, "a112", node)
// tab[1]=Node(1, 1, "a1", null)
final void treeifyBin(Node<K, V>[] tab, int hash) {
int n, index;
Node<K, V> e;
// eg6: tab !=null, tab.length=16
/** 当tab的长度小于64,则不转换为数,只扩展数组大小 */
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) {
// eg6: 执行resize()
resize();
/** 如果新增的node要插入的数组位置已经有node存在,则取出该位置的node为e */
} else if ((e = tab[index = (n - 1) & hash]) != null) {
/** 头节点 */
TreeNode<K, V> hd = null;
/** 尾节点 */
TreeNode<K, V> tl = null;
do {
/** 将Node转换为TreeNode->new TreeNode<>(p.hash, p.key, p.value, next); */
TreeNode<K, V> p = replacementTreeNode(e, null);
/** 将每个Node转换尾TreeNode,并且用prev和next指针进行链接,并以hd为头节点 */
if (tl == null) {
hd = p;
} else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
/** 如果为头节点的hd不为空,则进行数形化操作 */
if ((tab[index] = hd) != null) {
hd.treeify(tab);
}
}
}
满足树形化条件则调用了treeify方法,至此所有的执行操作基本了解了
总结
HashMap容器逻辑还是比较复杂的,大家可以针对注释和eg例子,再配合流程图,就能够很好的理解HashMap的put过程,至于其它逻辑比较简单,就没有详细说明
使用流程图来看看整体的put操作:
如果喜欢我的文章记得一定要
一键三连
哟(别下次一定!!!)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/17880.html