resize修炼手册
1. 扩容第一步: 确定新数组的 容量 和 阈值(极限值/临界值)
#### 1. 扩容第一步: 确定新数组的 容量 和 阈值(极限值/临界值)
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//拿到oldTab,在第2步操作时候会用到,在此可以带过。
//初次put()的时候旧容量=0,其余情况旧容量为数组长度。
//此处亦说明了hashmap初始化的时候容量其实是0,只有当第一次进行put操作时,才会真正的初始化。
int oldCap = (oldTab == null) ? 0 : oldTab.length;
/**
*原阈值oldThr=threshold,threshold是int类型全局变量,默认初始值是0;
*如果new操作调用的无参构造,即没有传入initialCapacity的时候,使用的threshold==0,反之则不为零;
*而后每次扩容都调用了tableSizeFor(initialCapacity),然后再把返回值赋给threshold;
*tableSizeFor(..)方法的作用是将传入的initialCapacity规范成2的n次方;
*因而oldThr(原阈值)始终>=0;
*/
int oldThr = threshold;
int newCap, newThr = 0; //这两个变量分别代表了 新容量 和 新阈值
if (oldCap > 0) {//当旧容量>0,即非第一次put,第一次插入跳过这里
if (oldCap >= MAXIMUM_CAPACITY) {//旧容量超过最大容量时,不超过则跳过这里
/**将当前阈值threshold = Integer.MAX_VALUE 。
*(tip1 : Integer.MAX_VALUE = 0x7fffffff ,是 int 类型 能最大表示值)
*(tip2 : MAXIMUM_CAPACITY = 1<<30 ,即Integer.MAX_VALUE == MAXIMUM_CAPACITY*2 -1)
*/
threshold = Integer.MAX_VALUE;
return oldTab;
}
//旧容量扩容一倍 也未超出 最大容量MAXIMUM_CAPACITY 的情况下
//值得注意的是,无论判断通不通过,这里的newCap已经被赋值,2倍于旧容量
== else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY) ==
newThr = oldThr << 1; // double threshold //判断通过,阈值也变为原来的2倍
}
//以下**else if** 和 **else** 对应了 第一次 put 操作 的**两种情况**:
//(1)进入此代码块的先决条件是(oldCap == 0 & 原阈值oldThr > 0 )
//即在创建hashmap对象时候使用的是有参构造。(构造可见附录)
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
//(2)创建hashmap对象时调用的是无参构造,使用默认的两个变量作为初始化参数
else { // zero initial threshold signifies using defaults
//默认初始化容量DEFAULT_INITIAL_CAPACITY==16(第一次put后的数组长度)
newCap = DEFAULT_INITIAL_CAPACITY;
//DEFAULT_LOAD_FACTOR 是 源码默认的负载因子,是一个浮点型变量,值为0.75f
//第一次put之后的阈值= (int)(0.75f*16),即为 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
//进入此代码块的先决条件是 ,程序运行通过了 上一个if判断里 **嵌套的if判断**
//即 旧容量 >= 最大容量 的情况。
if (newThr == 0) {
//注意:此处的newCap已经是旧容量的2倍;
//而loadFactor又分为 **默认值** 和 **非默认值** **两种情况**:
//(1)通过HashMap(int initialCapacity, float loadFactor)构造传入的是为非默认值情况,将使用传入的参数作为负载因子;
//(2)通过其余三个构造都是使用的默认值DEFAULT_LOAD_FACTOR == 0.75f
//比如: public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//如此,确定了各种情况下的新数组 新容量newCap , 新阈值newThr ;
//将threshold = newThr 。
threshold = newThr;
↑ 上下代码相连 ↓
2. 扩容第二步:将旧数组内容 复制到 新数组 当中(扩容的本质是 新数组替代旧数组)
####2. 将旧数组内容 复制到 新数组 当中(扩容的本质是 新数组替代旧数组)
//根据上面的newCap创建一个数组对象,准备复制操作
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;//将table的引用指向新数组对象
if (oldTab != null) {//如果oldTab是非空的,即有进行put操作而引发过初始化的
for (int j = 0; j < oldCap; ++j) {//遍历数组
Node<K,V> e;
if ((e = oldTab[j]) != null) {//当前元素不为空(如果是空,没有必要复制到新数组)
oldTab[j] = null;//在赋值给e之后,此数组元素设为null
if (e.next == null)
//如果只是数组部分有元素,即元素的next不指向一个对象,是为null
//利用 hash算法的返回值 和 (新容量newCap - 1) 进行与运算(&);
//结果作为目标元素在新数组中的索引,用以确定重排序的存储位置
//tip1: hash算法(调用hashCode()结果值的高16位和低16位进行异或^运算后 返回结果)
//tip2: 0 ~ newCap-1 共newCap 长度;
// **保证容量newCap为2的n次方,可以保证数据的散列性!!**
// 因为当newCap为2的n次方时,其值减1一定是01......1(首位0+全1)
// 当newCap不为2的n次方时,其值减1一定不是01......1,而是0、1掺杂
//比如:15==>01111 ,减1 = 01110 ,与hash算法的返回值与运算结果,
//无论hash算法的返回值最后一位是0还是1,与运算之后都是0,这将增加hash碰撞的可能性。
//为了降低碰撞的可能性,特意将newCap向2的n次方看齐,使其减1后有最多的1,
// 再和hash返回值进行&运算,保证散列性。
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)//若果当前元素时红黑树对象时
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//调用split()方法拆分重排
else { // preserve order//若果当前对象是链表中的一员
//loHead,loTail分别代表 **不需要移动** 的 **链头** 和 **链尾**
Node<K,V> loHead = null, loTail = null;
//hiHead ,hiTail 分别代表 **需要移动** 的 **链头** 和 **链尾**
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
//循环确定原链表中每一个元素的去向,分两种情况:
//(1)要么是新索引 = 原索引 ;
//(2)要么是新索引 = 原索引 + 旧容量 ;
//因为newCap-1比oldCap-1只是多了一位1,然后通过和hash算法返回值与运算
//返回索引值,将当前元素存入对应的下标(对应的链表中)。
//再次与hash返回值与运算的时候,决定元素去留的只是最高位与运算的结果。
do {
next = e.next;
//e.hash & oldCap ===> e.hash & 10....0
//最高位决定去留(即最高位的1和hash返回值 与运算的结果作为对象在新数组的索引)
//(“去留”在这里 表示 在新数组中是否保持和旧数组相同的索引(或理解为下标)
//hash和最高位的与运算结果若为0,说明在新数组保持和当前一样的索引
if ((e.hash & oldCap) == 0) {//jdk1.8 :尾插法
if (loTail == null)//链尾为空==>首次插入,将链尾指向e
loHead = e;
else
//链尾有元素,链尾的next指向e,当有多个元素时候,while()完成链表的追加。
loTail.next = e;
loTail = e; //使得链尾指向最新插入的元素/对象
}
else {//与运算结果为1,说明当前元素在新数组的索引要发生改变了。
if (hiTail == null)//链尾为空==>是首次插入
hiHead = e; //链头指向当前元素(区别于之前链头)
else
//链尾有元素,链尾的next指向e,当有多个元素时候,while()完成链表的追加。
hiTail.next = e;
hiTail = e;//新插入的元素作为链尾(jdk1.8:尾插法)
}
} while ((e = next) != null);
//将不需要移动的链表拼接到与旧数组相同的索引(下标)位置
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
//将需要移动的链表拼接到新索引(旧索引+旧容量oldCap)位置
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;//返回扩容后的对象
}
附录
(1)int hash(Object key)
·key != null 时 ,调用hashCode()方法将返回值高低16位异或^运算后返回
static final int hash(Object key) {
int h;
//1.key == null, 返回null
//2.key != null, 调用hashCode()返回一个int类型32位数值,
// 让结果的高16位和低16位进行异或^运算后返回。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(2)int hashCode() 本地(native)方法
·返回一个32位的int类型结果值,因为是一个算法,相同的对象一定有相同的hashCode值
public native int hashCode();
(3)HashMap四个构造方法
①HashMap(int initialCapacity, float loadFactor)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
②HashMap(int initialCapacity)
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
③HashMap()
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
④HashMap(Map<? extends K, ? extends V> m)
/**
* Constructs a new <tt>HashMap</tt> with the same mappings as the
* specified <tt>Map</tt>. The <tt>HashMap</tt> is created with
* default load factor (0.75) and an initial capacity sufficient to
* hold the mappings in the specified <tt>Map</tt>.
*
* @param m the map whose mappings are to be placed in this map
* @throws NullPointerException if the specified map is null
*/
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}