今天在看HashMap源码时,对于扩容部分的哈希运算不是很理解,网上查阅资料也没有发现有价值的解释,自己一个人口了半天,终于明白了原理,
首先把扩容部分的源码贴上来:
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { 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; }
以上源码就不加注释了,这里只看我们关注的部分的源码
do { next = e.next; if ((e.hash & oldCap) == 0) {//如果结果为0,则节点依然放入扩容后的新数组的原位置,即数组下标不变 if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else {//如果结果不为0,则节点放入扩容后的新数组的[原位置 + 原长度]的下标位置 if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; }
} while ((e = next) != null);
上面两行注释是通过源码直译过来的解释说明,但是并没有详细说明为什么,到底为什么呢?
让我们来吧这个问题具体一下:
假设数组长度是x,x = 2^k(因为长度是2的倍数),某个节点的hash值a,对应索引下标是b,即取模结果是b,则表达式如下
b = a % x,或者:b = a & (2^k - 1),
按照上面代码的逻辑,则如果 0 = a & x,即0 = a & 2^k,则b = a % 2x , 即 b = a & (2^(k+1) - 1),这究竟是为什么?
为什么?
为什么?
为什么?
当我吧这些数都转换成二进制表示的时候,突然就发现规律了,
首先x一定是只有一位是1,其余位全是零的正整数,即 ?1 ?,1的前面有多少个0不知道,后面有多少个0也不知道,当然也无所谓,我们假设这个位置是 i
假设 0 = a & x,则a在第 i 位一定是0,即?0 ?,当x扩容为原来的2倍以后,2^(k+1)二进制表示是 ?1 0 ?,此时第 i 位是0,第 i + 1 位是 1,则此时,
2^(k+1) - 1 用二进制表示一定是 ?0 1 ?(此时后面的?代表不知道多少个 1 ),如此,将a % 2x相当于
? ? ? 0 ? ? ?
& ? ? 0 1 1 1 ?
其结果与 a % x
? ? ? 0 ? ? ?
& ? ? 0 0 1 1 ?
的结果一定是相同的,因为a没有变,a的第i位是0,x只是第i位从0变成了1,但无论是0 是1 ,其与0进行 & 运算,结果都是0,因此结果也不会变化