前言
這是一篇對 transfer 方法的拾遺,關於之前那篇文章的一些一筆帶過,或者當時不知道的地方進行回顧。
疑點 1. 為什么將鏈表拆成兩份的時候,0 在低位,1 在高位?
回顧一下 transfer 的相關代碼:
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
// 取於桶中每個節點的 hash 值
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {// 如果最后更新的 runBit 是 0 ,設置低位節點
ln = lastRun;
hn = null;
}
else {
hn = lastRun; // 如果最后更新的 runBit 是 1, 設置高位節點
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
// 如果與運算結果是 0,那么就還在低位
if ((ph & n) == 0) // 如果是0 ,那么創建低位節點
ln = new Node<K,V>(ph, pk, pv, ln);
else // 1 則創建高位
hn = new Node<K,V>(ph, pk, pv, hn);
}
關鍵看上面注釋的代碼,如果 runBit 是 0,那么就設置在低位節點,反之,如果是 1,設置在高位。
為什么這么設計呢?當時樓主一筆帶過,稱之為這個貌似沒有什么特殊含義,實在是愚蠢之極。
今天解釋一下。
這要從 ConcurrentHashMap 的取於下標算法開始說起。
我們知道,在 putVal 方法中,會通過取於對象的 hash 值獲取下標。具體代碼如下:
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
也就是 (n - 1) & hash),這個 n 就是 length。這個其實相當於 hash % n(n 必須是2的指數)。但是比 % 更高效。
復習一下與運算:第一個操作數的的第n位於第二個操作數的第n位如果都是1,那么結果的第n為也為1,否則為0.
然后開始推導:
(n - 1) & hash),取於算法。
假設,我們的 table 長度是 16,也就是 10000,減一就是 01111. 取於下面這個數。這個數特別之處在於,
他的右起第 5 位是 0。如果是 10000 & 這個數,結果是 0.
000000001111 000000010000
010101001001 // 結果 9 010101001001 // &運算結果: 0
當我們擴容后,16 變成 32,也就是 10000. 再看看 (n - 1) & hash) 的結果:
000000011111
010101001001 // 結果還是 9
從這里可以看出,如果 & 運算是 0 ,那么即使擴容,下標也是不變的。
再看看另一種情況,換一個 hash 數字,右起第五位是 1 :
000000001111 000000010000
010101010001 // 結果 1 010101010001 // &運算結果: 1
這里的 & 與運算后,結果是 1,和上面的不同。同時, (n - 1) & hash) 的結果也是 1.
當擴容后,結果是什么樣子呢?
000000011111
010101010001 // 結果變化:10001 == 17
可以看到,(n - 1) & hash) 的結果是 17,17 - 1,剛好是 16,而這個 16 的原因是我們的二進制進了一位。
現在明白了吧?0 在低位,1 在高位不是隨便設計的。這里讓我想到了一致性 hash 算法:當桶的數量變化了,那么 hash 的位置也會變化。
這里的設計是為了防止下次取值的時候,hash 不到正確的位置。
實際上,JDK 1.8 的 HashMap 也是這么實現的重新散列。文章深入理解 HashMap put 方法(JDK 8逐行剖析)。其中 resize 方法和這里高度類似。
疑點 2:為什么會有 i >= n || i + n >= nextn 的判斷?
回顧一下代碼:
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
這個判斷在當時看來是沒有可能存在的。到現在也沒明白為什么。。。。
如果有大佬知道,請指點一二。
