1、扩容过程中的红黑树元素转移方法-split
在这个方法中有一个重要的常数,反树化的阈值6,而树化的阈值是8,之所以不等,是防止单个元素增加删除造成的频繁树化和反树化的过程,反而降低效率。
private static final int UNTREEIFY_THRESHOLD = 6;
/**
* 红黑树元素转移
*
* @param map 当前Map
* @param tab 扩容后的新数组
* @param index 索引位置
* @param bit 扩容前的容量
* @return void
* @Author muyi
* @Date 16:24 2020/7/31
*/
final void split(HashMap<K, V> map, HashMap.Node<K, V>[] tab, int index, int bit) {
/**
* loHead-低位红黑树头节点,loTail-低位红黑树尾节点,hiHead-高位红黑树头节点,hiTail-高位红黑树尾节点,next-后继节点,b-根节点
* lc-低位红黑树节点的个数,hc-高位红黑树的节点个数
* 因为oldCap是2的幂次方,所以e.hash & oldCap 这个计算结果要么是0,要么是1,
* 当计算结果为1时,放在高位中,结果为0时,放在低位中
*/
HashMap.TreeNode<K, V> b = this;
HashMap.TreeNode<K, V> loHead = null, loTail = null;
HashMap.TreeNode<K, V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (HashMap.TreeNode<K, V> e = b, next; e != null; e = next) {
next = (HashMap.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;
}
}
// 如果红黑树的个数小于UNTREEIFY_THRESHOLD(6)时,将红黑树转化成链表
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
// 转化成普通链表
tab[index] = loHead.untreeify(map);
else {
/**
* 大于6的时候,将头节点放再数组的对应索引位置
* 如果高位链表不存在,说明树形结构未改变,整个元素转移的过程结束,
* 如果高位链表存在,说明原来的一颗红黑树被拆成了两棵红黑树,就需要重新树化
*/
tab[index] = loHead;
if (hiHead != null)
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);
}
}
}
2、反树化过程中将TreeNode转换成Node节点-untreeify
/**
* 将红黑树还原成链表
*
* @param map
* @return com.example.bd.javaee.hashmap.HashMap.Node<K, V>
* @Author muyi
* @Date 16:33 2020/7/31
*/
final HashMap.Node<K, V> untreeify(HashMap<K, V> map) {
HashMap.Node<K, V> hd = null, tl = null;
for (HashMap.Node<K, V> q = this; q != null; q = q.next) {
// 将TreeNode转换成Node
HashMap.Node<K, V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
3、扩容方法-resize
/**
* 扩容阈值
*/
int threshold;
/**
* 最大容量
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。
* loadFactor的默认值为0.75f。
* 计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity
*/
final float loadFactor;
/**
* 负载系数,当数据量达到hashMap的容量与该值的乘积时,将会进行扩容操作
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
final HashMap.Node<K, V>[] resize() {
HashMap.Node<K, V>[] oldTab = table;
// 扩容前的容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 扩容前的扩容阈值
int oldThr = threshold;
// 新的阈值设置为0;
int newCap, newThr = 0;
// 原始容量大于0,进行扩容操作
if (oldCap > 0) {
// 如果已经扩容至最大值,则不再允许扩容,直接返回原table数组
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
// 扩容一次性扩容2倍,如果扩容后的值位于 [默认初始容量16,最大值) 之间,则扩容成功
} else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 无论容量值是否修改成功,阈值都在原基础上扩大2倍。
newThr = oldThr << 1;
// 如果原始容量 = 0(原始容量值不可能存在负数),并且阈值大于0,则直接设置新容量的值与原阈值相等
} else if (oldThr > 0)
// 注意这个分支,这里只对新的容量值进行了修改,但是新的阈值未进行修改,还是原始值0,这也就是后面判断(newThr == 0)的情况
newCap = oldThr;
// 如果阈值和原容量值均等于0,则直接初始化新阈值和容量等于默认值
else {
newCap = DEFAULT_INITIAL_CAPACITY;// newCap = 16;
newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // newThr=(int)16 *0.75
}
if (newThr == 0) {
// 计算一个新的阈值
float ft = (float) newCap * loadFactor;
/**
* 设时候如果同时满足newCap、ft均小于最大容量值,则说明阈值合法,否则阈值就设置为最大整数值。
* 个人理解:这里之所以做这样的判断,是因为在上面新的容量值直接设置成了原阈值,阈值是可以在实例化的时候指定的,
* 防止人为的失误,造成阈值或者容量超过最大容量
*/
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ?
(int) ft : Integer.MAX_VALUE);
}
// 修改HashMap的阈值
threshold = newThr;
@SuppressWarnings({"rawtypes", "unchecked"})
HashMap.Node<K, V>[] newTab = (HashMap.Node<K, V>[]) new HashMap.Node[newCap];
table = newTab;
// 当oldTab不为空的时候,将原数组中的元素移动到新的数组中
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
HashMap.Node<K, V> e;
if ((e = oldTab[j]) != null) {
// 原数组中的元素置空
oldTab[j] = null;
// 如果后继节点为null,说明当前位置只有一个元素,直接计算位置,将该值赋给新数组的对应位置即可
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果该数据类型是红黑树类型节点
else if (e instanceof HashMap.TreeNode)
((HashMap.TreeNode<K, V>) e).split(this, newTab, j, oldCap);
else {
/**
* 普通类型的节点(链表元素转移)
* 在对链表进行转移的时候,这里与1.7有一点点区别,1.7是一个一个的计算转移,这里是先计算再转移
* loHead-低位头节点,loTail-低位尾节点,hiHead-高位头节点,hiTail-高位尾节点,next-后继节点
* 因为oldCap是2的幂次方,所以e.hash & oldCap 这个计算结果要么是0,要么是1,
* 当计算结果为1时,放在高位链表中,结果为0时,放在低位链表中
*/
HashMap.Node<K, V> loHead = null, loTail = null;
HashMap.Node<K, V> hiHead = null, hiTail = null;
HashMap.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);
// 在执行了上面的操作后,如果尾节点不为空,说明尾节点指向了最后一个节点,所以需要将尾节点指向null,
if (loTail != null) {
loTail.next = null;
// 在数组的索引位置放入头节点
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
// 高位链表放置的索引位置需要加原链表的长度,元素分散放置
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}