1、在 jdk1.8 中,resize()方法是在 hashmap 中的键值对大于阀值时或者初始化时,就调用 resize()方法进行扩容;
2、每次扩展的时候,都是扩展 2 倍;
3、扩展后 Node 对象的位置要么在原位置,要么移动到原偏移量两倍的位置。在 putVal()中,我们看到在这个函数里面使用到了 2 次 resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值(第一次为 12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是 JDK1.8 版本的一个优化的地方,在 1.7 中,扩容之后需要重新去计算其 Hash 值,根据 Hash 值对其进行分发,但在 1.8 版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为 0,重新进行hash 分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上
final Node <K, V> [] resize(){
Node <K, V>[] oldTab = table;//oldTab 指向 hash 桶数组
int oldCap =(oldTab == null)? 0 :oldTab.length;
int oldThr = threshold;
int newCap,newThr = 0;
if(oldCap>0){ //如果 oldCap 不为空的话,就是 hash 桶数组不为空
if (oldCap >= MAXIMM_CAPACITY)(//如果大于最大容量了,就赋值为整数最大的
阀值
threshold = Integer.MAX_VALUE;
return oldTab;//返回
}//如果当前 hash 桶数组的长度在扩容后仍然小于最大容量并且oldCap 大于默认值16
else if((newCap = oldCap<<1)<MAXIMML_CAPACITY && oldCap >=
DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1;// double threshold 双倍扩容阀值
threshold
}
// 旧的容量为 0,但 threshold 大于零,代表有参构造有 cap 传入,threshold 已经被初始化成最小 2 的n 次幂
//直接将该值赋给新的容量
else if (oldThr>0)// initial capacity was placed in threshold
newCap = oldThr;
//无参构造创建的 map,给出默认容量和 threshold 16, 16*0.75
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr =(int)(DEFAULT_LOAD_FACTOR * DBFAULT_INTTIAL_CAPACITY);
}
//新的 threshold =新的 cap * 0.75
if (newThr == 0){
float ft=(float) newCap * loadFactor;
newThr = (newCap<MAXIMUM_CAPACITY && ft <(float) MAxIMUM_CAPACITY?(int) ft: Integer.MAX_VALUE);
}
threshold = newThr;
//计算出新的数组长度后赋给当前成员变量table
@SuppressWarnings({"rawtypes","unchecked"})
Node < K, V>[] newTab =(Node < K, V>[]) new Node [newCap];//新建 hash 桶
教组
table = newTab;//将新数组的值复制给旧的 hash 桶数组
// 如果原先的数组没有初始化,那么 resize 的初始化工作到此结束,否则进入扩容元素重排逻辑,使其均匀的分散
if (oldTab != null){
//遍历新数组的所有桶下标
for (int j = 0;j < oldCap:++j){
Node<K,V> e:
if((e = oldTab[j])!= null){
//旧数组的桶下标赋给临时变量 e,并且解除旧数组中的引用,否则就数组无法被 GC 回收
oldTab[j]= null;
//如果 e.next==null,代表桶中就一个元素,不存在链表或者红黑树
if (e.next == null)
//用同样的 hash 映射算法把该元素加入新的数组
newTab[e.hash & (newCap – 1)] =e;
//如果 e是 TreeNode 并且 e.next!=null,那么处理树中元素的重排
else if (e instanceof TreeNode)
((TreeNode < K, V>)e).split(this, newTab, j, oldCap);
//e是链表的头并且e.next!=null,那么处理链表中元素重排
else {// preserve order
//loHlead,1oTail 代表扩容后不用变换下标,见注 1
Node< K, V> loHead = null, loTail = null;
// hiHead, hiTail 代表扩容后变换下标,见注 1
Node< K, V> hiHead = null, hiTail = null;
Node< K, V> next;
//遍历链表
do {
next = e.next;
if((e.hash & oldCap)== 0){
if (loTail == null)
//初始化 head 指向链表当前元素 e, e 不一定是链表的第一个元素,初始化后 1oHead
//代表下标保持不变的链表的头元素
loHead = e;
else
// 1oTail. next 指向当前 e
loTail.next =e;
// 1oTail 指向当前的元素 e
//初始化后,loTail 和 loHead 指向相同的内存,所以当
loTail.next 指向下一个元素时,
//底层数组中的元素的 next 引用也相应发生变化,造成
lowHead.next.next…..
//跟随 loTail 同步,使得 1owHead 可以链接到所有属于该链表的元素。
loTail = e;
} else {
if (hiTail == null)
//初始化 head 指向链表当前元素 e,初始化后 hiHead 代表下标更改的链表头元素
hiHead = e;
else hiTail.next = e;
hiTail = e;
}
} while ((e = next)!= null);
//遍历结束,将 tail 指向 null,并把链表头放入新数组的相应下标,形成新的映射。
if (loTail != null){
loTail.next = null;
newTab[j]= loHead;
}
if (hiTail != null){
hiTail.next = null;
newTab[j + oldCap]= hiHead;
}
}
}
}
}
return newTab;
}
Was this helpful?
0 / 0