講講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衝突的概率就降低了!