持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
大家好,我是尚影嫣🌷,一名Java后端程序媛。如果您喜欢我的文章,欢迎点赞➕关注❤️,让我们一起成为更好的我们~🥰
HashMap源码解析
resize( )扩容方法
resize( )是HashMap中扩容的方法,当HashMap中存的数据量大于threshold时,或进行初始化HashMap的时候会进行扩容的操作,会调用resize()方法进行扩容。
执行resize( )方法有两种情况:
- 在HashMap的putVal( )方法中,会先判断table是否为空,为空会执行resize( ),再初始化table。
- 在HashMap中的存储的数据量大于threshold时,会执行resize( )方法。
/**
* 对原table进行扩容,并返回扩容后的Node数组即HashMap的最新数据
*/
final Node<K,V>[] resize() {
//table赋予oldTab作为扩充前的table数据
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;
}
//若新表大小(oldCap*2)小于数组极限大小 并且 老表大于等于数组初始化大小
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//旧数组大小oldThr,经二进制运算向左位移1个位置,即oldThr*2当作新数组的大小
newThr = oldThr << 1; // double threshold
}
//若原table中下次扩容大小oldThr大于0
else if (oldThr > 0)
//将oldThr赋予控制新表大小的newCap
newCap = oldThr;
else { //若其他情况则将获取初始默认大小
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//若新表下一次扩容大小为0
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
table = newTab;
if (oldTab != null) {
//若oldTab中有值需要通过循环将oldTab中的值保存到新表中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//获取老表中第j个元素 赋予e
if ((e = oldTab[j]) != null) {
//并将老表中的元素数据置Null
oldTab[j] = null;
//若此判定成立 则代表e的下面没有节点了
if (e.next == null)
//将e直接存于新表的指定位置
newTab[e.hash & (newCap - 1)] = e;
//若e是TreeNode类型
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循环 获取新旧索引的节点
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;
}
总结
- 数组在扩容时,容量会扩为原来的两倍。
- 在扩容过程中,会先创建⼀个长度为原来两倍新的table数组,同时旧table中链表会进行重哈希,根据hash后得到的不同结果,形成两条链表。一条放在数组下标与原来相同的下面,另一条放在原来的旧数组下标值+扩容的长度的下面。