老规矩先说下个人研究分析结果:
- 当实例化HashMap的时候没有指定加载因子和初始化容量的时候,表初始容量使用默认值DEFAULT_INITIAL_CAPACITY=16,表的容量阈值threshold=默认加载因子*默认初始化容量=(DEFAULT_LOAD_FACTOR*DEFAULT_INITIAL_CAPACITY)=(16*0.75)=12
- 当实例化HashMap的时候指定加载因子和初始化容量(不大于2的30次方)的时候,表初始容量值为最小的大于等于初始化容量的二次幂即threshold, 然后新的容量阈值=原来的容量阈值*加载因子=原threshold*加载因子,这里有个前提是原来容量值和新计算得到的容量阈值不能大于2的30次方,否则新的容量阈值threshold=Integer类型的最大值
- 当表后续扩容的时候,扩容的大小取决于之前表容量(旧表)的大小和容量阈值,当旧表容量大于2的30次方,则直接返回旧表,不再进行扩容。容量阈值threshold=Integer类型的最大值。否则新表容量等于旧表容量左移一位(扩大一倍),此刻如果旧表容量左移一位依然小于2的30次方,同时旧表的容量大于等于DEFAULT_INITIAL_CAPACITY=16,则新的容量阈值threshold=旧容量阈值左移一位(扩大一倍)。
- 基于上述3种情况得到创建新表的参数 newCap 和newThr(threshold--用于下次调用扩容方法时候的判断) 创建一个新的newCap长度的表(数组)。然后通过for循环的方式依次读取旧表的值填充到新表对应的位置。这个过程会经过一系列的判断运算(这里暂时不做进一步的研究)。这是一个非常耗时的过程。因此如何通过合理的设置hashMap的加载因子和初始化容量值来尽可能的避免二次扩容问题也就非常重要了。这也是HashMap性能优化的重点
resize()具体代码分析
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
/**
*上述源码英文对resize()方法解释的意思大概就是初始化或者加倍表的大小,如果表容量为null 给表分配一个和*初始化容量目标约束字段threshold值一样的容量。否者,由于我们使用的是2的次方的扩展,这个每个二进制*文件元素必须要么保持索引不变,要么在新表移动一个2个偏移量的次方。
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//获取到待调整表(容器)赋值给oldTab
int oldCap = (oldTab == null) ? 0 : oldTab.length;//判断oldTab长度赋值给oldCap
int oldThr = threshold;//把之前容量阈值赋值给oldThr
int newCap, newThr = 0;//声明两个新变量 用于接收表调整大小后的值
//判断旧容量大小,如果表容量大于0,进入if判断内
if (oldCap > 0) {
//如果表容量大于定义的最大容量值MAXIMUM_CAPACITY,则把Interger类型的最大值赋值给容量阈值属性threshold,不调整表的大小,直接返回待调整表。
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//旧表容量向左位移一位(扩大一倍)后得到值赋值给newCap,否则如果此时得到的新容量值还是小于最大容量值而且oldCap大于默认等于默认值初始化容量值DEFAULT_INITIAL_CAPACITY=16,则把旧的容量阈值左移一位(扩大一倍赋值给newThr)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//否则如果旧的容量阈值大于0 则把旧容量阈值赋值给newCap 表的新容量
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//如果上边判断都没进入 则说明threshold 容量阈值为0 表还没有初始化。此时我们设置表的newcap等于默认容量值DEFAULT_INITIAL_CAPACITY=16,表的newthr=(默认加载因子(0.75)*默认初始容量)=12
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//如果newThr==0进入判断,用于容量阈值不为0但是表容量是0的情况,即初始化hashMap的时候传参了initialCapacity。此时新的容量阈值取决于newCap新容量值和ft=newCap*加载因子,如果ft和newCap两者值都小于最大容量值MAXIMUM_CAPACITY,则newthr=ft,否则=Integer类型最大值
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//把得到的newThr赋值给容量阈值变量threshold
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//初始化一个newCap大小的node节点数组
table = newTab;//把table指向新建立的newTab数组
if (oldTab != null) {//如果旧表不为空,则开始迁移旧数组到新newTab
for (int j = 0; j < oldCap; ++j) {//for循环读取经过一系列的判断运算迁移到新表
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)
((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;
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;
}