java集合--HashMap(四)
阿新 • • 發佈:2019-02-06
java集合–HashMap(四)
1.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;
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;
}
HashMap 在進行擴容時,使用的 rehash 方式非常巧妙,因為每次擴容都是翻倍,與原來計算(n-1)&hash 的結果相比,只是多了一個 bit 位,所以節點要麼就在原來的位置,要麼就被分配到“原位置+舊容量”這個位置。例如,原來的容量為 32,那麼應該拿 hash 跟 31(0x11111)做與操作;在擴容擴到了 64 的容量之後,應該拿 hash 跟 63(0x111111)做與操作。新容量跟原來相比只是多了一個 bit 位,假設原來的位置在 23,那麼當新增的那個 bit 位的計算結果為 0時,那麼該節點還是在 23;相反,計算結果為 1 時,則該節點會被分配到 23+31 的桶上。正是因為這樣巧妙的 rehash 方式,保證了 rehash 之後每個桶上的節點數必定小於等於原來桶上的節點數,即保證了 rehash 之後不會出現更嚴重的衝突。
2.HashMap的遺留小問題
- 在java1.8以後,當插入值時如果出現衝突,會以連結串列的形式放在後面,如果數量超過8個就會轉化為紅黑樹。同樣的道理,當刪除的時候,如果數量小於6個就會恢復鏈式結構。因為紅黑樹的平均查詢長度為(log n),連結串列的平均查詢長度為n/2。d當長度為8時,紅黑樹平均查詢長度為3。如果使用連結串列,平均查詢程度高度為4。所以8是連結串列轉化為紅黑樹的臨界點。如果長度在6以內,是沒有必要轉化的,況且連結串列轉化為紅黑樹也是需要時間的。
3.HashMap的小總結
hashCode()和equals()方法是理解HashMap的基石,很重要。重寫equals()方法就必須重寫hashCode()方法。
HashMap可以允許null作為key和value.
HashMap是非線性安全的,只適用於單執行緒環境下。在多執行緒情況下可以使用ConcurrentHashMap,或者可以使用Collections中的synchronizedMap()方法將HashMap轉化為執行緒安全。
ConcurrentHashMap是採用分段鎖的機制,它的效率比Hashtable高,且是執行緒安全的。具體什麼原理,建議讀者去自己去查詢資料,這裡不再進行闡述,或者在後面的文章中我會更新。