深度解析HashMap
講講HashMap?
源碼解析
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//輔助變量
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果當前tabe數組是null,數量是0的話
if ((tab = table) == null || (n = tab.length) == 0)
//執行擴容方法resize()
//初始化數組默認大小是16
n = (tab = resize()).length;
//通過hash與運算獲得數組索引位置,該位置是null
if ((p = tab[i = (n - 1) & hash]) == null)
//創建新Node對象
tab[i] = newNode(hash, key, value, null);
else {//該索引位置不為null
Node<K,V> e; K k;
//當前table索引hash和新的索引hash相同 &&
//(當前table節點的key和新key是同一對象 || equals為真)
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果當前table的node已是紅黑樹就按紅黑樹處理
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果節點是後面是鏈表就遍歷比較
for (int binCount = 0; ; ++binCount) {
//遍歷整個鏈表結束,也沒有和新的key相同的就添加鏈表後面新創建node
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//加入後判斷當前的鏈表個數是否已經達到8個,如果達到就進行紅黑樹轉換
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//提示:
//treeifyBin里如果table為null或者大小小於64,暫時不會轉化為紅黑樹
//而是進行擴容。
//if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
treeifyBin(tab, hash);
break;
}
//發現相同就break結束遍歷
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //替換原有的值,直接結束
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//每增加node,Size++
//如果數量大於臨界值,就進行擴容
//threshold=table大小(默認16)*負載因子(默認0.75)
//12>24>>48
if (++size > threshold)
//擴容成2倍增長16->32->64
resize();
afterNodeInsertion(evict);
return null;
}
簡述
Jdk1.7 數組+鏈表
Jdk1.8 數組+鏈表+紅黑樹
hashMap 默認數組大小16,負載因子 默認0.75, 臨界值= 數組大小*負載因子。
- 首先將key進行hash算法,key的hashCode右移16位並進行異或運算。
- 如果table是null ,先初始化數組默認大小是16
- 通過hash的與運算獲得當前table索引位置,如果索引位置內容是null則創建新Node節點對象
- 如果當前table索引位置里內容不為null,則尋找相同key直接修改值,分三種情況
- 當前table索引的對象hash是否與新hash相同並且當前table位置對象key是否和新key相同
- 如果當前table索引的對象已是TreeNode(紅黑樹)就進行遍歷樹方式查找
- 如果是鏈表則遍歷鏈表查找,如果遍歷完也沒找到就往鏈表尾部加入並創建新Node,在判斷當前大小是否大於等於8,如果大於就進行紅黑樹轉化。執行紅黑樹轉化方法時有個條件,如果數組大小小於64則還是進行擴容操作。
- 當前Size數組大小和threshold(臨界值)比較,如果size大於threshold則進行擴容(2倍增長),
問題
JDK 1.8中對hash算法和尋址算法是如何優化的?
//hash算法優化
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//(n - 1) & hash 尋址算法優化
if ((p = tab[i = (n - 1) & hash]) == null)
配合來說,put鍵值時,肯定涉及數組長度的取模運算,但是在計算機上面(n – 1) & hash位運算效率高於普通的除法取余。關鍵這個n(hashmap長度)通常會很小,但是hash值是32位的,因此(n-1)大概率高位補零,由於與運算是只有兩個數字都是1結果才為1,其他情況均為0,因此hash值的高16位永遠起不到作用,這樣就大大的增加了尋址衝突概率,除非數組長度很大,撐滿整個32位最好都為1,才不會有衝突,但一般不可能很長,所以通過hash算法的優化(h = key.hashCode()) ^ (h >>> 16) 這個公式完美的讓hash值高16位與低16位融合,保留高低16位的特徵。再跟(n-1)做與運算,hash衝突的概率就降低了!