HashMap 是基于 hashing 的原理
我們使用 put(key, value) 存儲對象到 HashMap 中,使用 get(key) 從 HashMap 中擷取對象。當我們給 put() 方法傳遞鍵和值時,我們先對鍵調用 hashCode() 方法,計算并傳回的 hashCode 是用于找到 Map 數組的 bucket 位置來儲存 Node 對象。
這裡關鍵點在于指出,HashMap 是在 bucket 中儲存鍵對象和值對象,作為Map.Node 。
以下是 HashMap 初始化
簡化的模拟資料結構:
Node[] table = new Node[16]; // 散列桶初始化,table
class Node {
hash; //hash值
key; //鍵
value; //值
node next; //用于指向連結清單的下一層(産生沖突,用拉鍊法)
}
複制
以下是具體的 put 過程(JDK1.8)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
// 第三個參數 onlyIfAbsent 如果是 true,那麼隻有在不存在該 key 時才會進行 put 操作
// 第四個參數 evict 我們這裡不關心
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 第一次 put 值的時候,會觸發下面的 resize(),類似 java7 的第一次 put 也要初始化數組長度
// 第一次 resize 和後續的擴容有些不一樣,因為這次是數組從 null 初始化到預設的 16 或自定義的初始容量
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 找到具體的數組下标,如果此位置沒有值,那麼直接初始化一下 Node 并放置在這個位置就可以了
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {// 數組該位置有資料
Node<K,V> e; K k;
// 首先,判斷該位置的第一個資料和我們要插入的資料,key 是不是"相等",如果是,取出這個節點
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 如果該節點是代表紅黑樹的節點,調用紅黑樹的插值方法,本文不展開說紅黑樹
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 到這裡,說明數組該位置上是一個連結清單
for (int binCount = 0; ; ++binCount) {
// 插入到連結清單的最後面(Java7 是插入到連結清單的最前面)
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// TREEIFY_THRESHOLD 為 8,是以,如果新插入的值是連結清單中的第 9 個
// 會觸發下面的 treeifyBin,也就是将連結清單轉換為紅黑樹
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 如果在該連結清單中找到了"相等"的 key(== 或 equals)
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 此時 break,那麼 e 為連結清單中[與要插入的新值的 key "相等"]的 node
break;
p = e;
}
}
// e!=null 說明存在舊值的key與要插入的key"相等"
// 對于我們分析的put操作,下面這個 if 其實就是進行 "值覆寫",然後傳回舊值
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 如果 HashMap 由于新插入這個值導緻 size 已經超過了門檻值,需要進行擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
複制
對 Key 求 Hash 值,然後再計算下标 如果沒有碰撞,直接放入桶中(碰撞的意思是計算得到的 Hash 值相同,需要放到同一個 bucket 中) 如果碰撞了,以連結清單的方式連結到後面 如果連結清單長度超過閥值(TREEIFY THRESHOLD==8),就把連結清單轉成紅黑樹,連結清單長度低于6,就把紅黑樹轉回連結清單 如果節點已經存在就替換舊值 如果桶滿了(容量16 * 加載因子0.75),就需要 resize(擴容2倍後重排)

以下是具體 get 過程
考慮特殊情況:如果兩個鍵的 hashcode 相同,你如何擷取值對象?
當我們調用 get() 方法,HashMap 會使用鍵對象的 hashcode 找到 bucket 位置,找到 bucket 位置之後,會調用 keys.equals() 方法去找到連結清單中正确的節點,最終找到要找的值對象。
記一次面試題: