JDK13-HashMap-resize源码解析
resize是重新散列,所以要在现在容量和阈值的基础上获取新的容量和阈值,函数首先进行了变量定义
final HashMap.Node<K,V>[] resize() {//resize函数的返回值是一个Node数组
HashMap.Node<K,V>[] oldTab = table;//transient Node<K,V>[] table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;//原容量
int oldThr = threshold;//之前求出的threshold赋值给原阈值
int newCap, newThr = 0;//resize之后的新容量和阈值
下面一段代码就是根据原容量和阈值计算新容量和阈值的方法
if (oldCap > 0) {//原Node数组非空,执行扩容操作
if (oldCap >= MAXIMUM_CAPACITY) {//MAXIMUM_CAPACITY=1 << 30
threshold = Integer.MAX_VALUE;//MAX_VALUE=1<<31 - 1
return oldTab;//表示已经到达最大容量,不能再扩容了
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)//DEFAULT_INITIAL_CAPACITY= 1<<4 = 16
newThr = oldThr << 1;
//左移一位是double,newCap和newThr都变成double
}
//下面oldCap = 0,对table进行初始化,确定初始的容量和阈值
else if (oldThr > 0)
newCap = oldThr;//原来求的阈值赋值给新容量
else {
newCap = DEFAULT_INITIAL_CAPACITY;//16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//16*0.75=12
}
if (newThr == 0) {//说明是(else if (oldThr > 0) newCap = oldThr;//原来求的阈值赋值给新容量)这种情况
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//将 新容量*输入的loadFactor 的值赋给新阈值,完成初始化
threshold = newThr;
确定新的table的容量和阈值之后,如果原来已保存数据,需要对原来的数据重新散列,使分布均匀,这是JDK8之后对JDK7的优化,不会使链表越来越长
HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new Node[newCap];//产生一个新的Node数组
table = newTab;//这一句说明只要初始化一次之后,table就不可能为0
if (oldTab != null) {//只有在非初始化的情况下,才重新分配数据
for (int j = 0; j < oldCap; ++j) {//++j表示先++,再赋值
HashMap.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)//e属于树节点
((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { //如果当前节点在链表方向上还有节点
//需要把链表上的所有节点重新分配
HashMap.Node<K,V> loHead = null, loTail = null;
HashMap.Node<K,V> hiHead = null, hiTail = null;
HashMap.Node<K,V> next;
do {
//解析见下文
} while ((e = next) != null);
if (loTail != null) {
//解析见下文
}
if (hiTail != null) {
//解析见下文
}
}
}
}
}
return newTab;
do···while函数就是把一个桶对应的链表上的每一个节点,利用e.hash & oldCap的值,把原来的一个链表拆成两个链表
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);
e.hash & oldCap 其实是取出e.hash上的一个标志位,因为oldCap总是2的某次方,所以oldCap用位表示是00xx10xx0的形式。
在e.hash中,oldCap中1对应的那一位,e.hash可能为0,可能为1
如果这一位是0,那么e.hash & (oldCap-1)就等于e.hash & (newCap-1)
如果这一位是1,那么于e.hash & (newCap-1)=e.hash & (oldCap-1)+oldCap
通过这种方法,把链表上的节点分到两个新的链表中,一个是lo链表,一个是hi链表
当这个链表上所有的节点都被分配完之后,再把新生成的链表放进newTab的桶里
if (loTail != null) {
loTail.next = null;//loTail和hiTail 的值使用之后,会把loTail和hiTail 清零
newTab[j] = loHead;//把loHead对应的新的链表放在原来的桶里
}
if (hiTail != null) {
hiTail .next = null;
newTab[j + oldCap] = hiHead;//把hiHead对应的新链表放在偏移oldCap的桶里
}
使用(e.hash & oldCap) == 0来进行分配的原因:可以参考
https://segmentfault.com/a/1190000015812438?utm_source=tag-newest