HashMap(1.8)
HashMap的resize方法:
代码如下:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//获取旧表的容量值
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//获取旧的阈值
int oldThr = threshold;
//定义新的容量、新的阈值
int newCap, newThr = 0;
//1.判断旧表容量值是否大于0
if (oldCap > 0) {
//判断旧表容量值是否大于最大容量值,如果大于或等于,则阈值取:2<<30,且返回旧表,这里相当于只是调整了阈值大小,没有对旧表大小进行实际改变
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//初始化新容量值为旧容量值的2倍,然后与最大容量值比较,当新容量值小于最大容量值,且旧容量值大于等于默认初始化容量值,这时也对新阈值赋值为旧阈值的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//2.这里的前提条件是oldCap=0,说明这是第一次创建表,将对新容量赋值为旧阈值,这里旧阈值其实就等于HashMap的初始化容量值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//3.当oldCap=0且oldThr=0的情况下,我们默认对新容量值和新阈值按初始化值进行赋值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//当进入判断条件2时,这时新阈值就会等于0
if (newThr == 0) {
//根据新容量值*加载因子得到新阈值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//修改全局变量threshold的值
threshold = newThr;
//定义并初始化新表,指定容量
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//修改全局变量table的值
table = newTab;
//判断旧表是否存在,如果存在,则需要将旧表中的数据复制到新表中
if (oldTab != null) {
//遍历旧表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//判断旧表中的每个位置上的元素是否存在,将该元素赋值给临时变量e
if ((e = oldTab[j]) != null) {
//将旧表元素置空,有利于GC
oldTab[j] = null;
//因为HashMap的数据结构是数组+链表,这里需要判断该元素所在链表是否有下个元素,如果没有,则可以直接将该元素放入新表中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = 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 {
//逐个取出链表中的元素
next = e.next;
//比较hash值,这里的目的是为了处理hash冲突,使用hiHead存储哈希冲突的元素
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;
}
HashMap的resize方法:
代码如下:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
//获取旧表的容量值
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//获取旧的阈值
int oldThr = threshold;
//定义新的容量、新的阈值
int newCap, newThr = 0;
//1.判断旧表容量值是否大于0
if (oldCap > 0) {
//判断旧表容量值是否大于最大容量值,如果大于或等于,则阈值取:2<<30,且返回旧表,这里相当于只是调整了阈值大小,没有对旧表大小进行实际改变
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//初始化新容量值为旧容量值的2倍,然后与最大容量值比较,当新容量值小于最大容量值,且旧容量值大于等于默认初始化容量值,这时也对新阈值赋值为旧阈值的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//2.这里的前提条件是oldCap=0,说明这是第一次创建表,将对新容量赋值为旧阈值,这里旧阈值其实就等于HashMap的初始化容量值
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//3.当oldCap=0且oldThr=0的情况下,我们默认对新容量值和新阈值按初始化值进行赋值
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//当进入判断条件2时,这时新阈值就会等于0
if (newThr == 0) {
//根据新容量值*加载因子得到新阈值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//修改全局变量threshold的值
threshold = newThr;
//定义并初始化新表,指定容量
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//修改全局变量table的值
table = newTab;
//判断旧表是否存在,如果存在,则需要将旧表中的数据复制到新表中
if (oldTab != null) {
//遍历旧表
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//判断旧表中的每个位置上的元素是否存在,将该元素赋值给临时变量e
if ((e = oldTab[j]) != null) {
//将旧表元素置空,有利于GC
oldTab[j] = null;
//因为HashMap的数据结构是数组+链表,这里需要判断该元素所在链表是否有下个元素,如果没有,则可以直接将该元素放入新表中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = 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 {
//逐个取出链表中的元素
next = e.next;
//比较hash值,这里的目的是为了处理hash冲突,使用hiHead存储哈希冲突的元素
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;
}
总结:
1、扩容操作首先主要的操作是给新表的容量以及阈值正确赋值
2、创建新表后,要判断节点元素是红黑树还是链表,分别做不同的处理
3、处理链表元素时要注意哈希冲突的元素要单独提取出来存储