直接上代碼
注: 代碼來自於 Java 9
put方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
- 當調用put(),首先會根據key生成一個 hash值,原理如下:
static final int hash(Object key) {
int h;
//key 是 null 直接返回 0
//key 不是null,先計算key對應的hashCode,賦值給 h
//並將 h 與 h >>> 16 做異或(相同為0 不同為1)運算 ,作為hash值返回
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
-
下圖舉例說明了位運算的過程,至於原理解釋,參考本文引用

-
拿到了hash值后,調用 putVal(),做了如下操作
- 將對象table賦值給tab,並以tab是否為空作為是否第一次調用此方法的判斷,是則resize()並給tab,n賦值;
- 獲取tab的第i個元素:根據 (n - 1) & hash 算法 ,計算出i找到,如果為空,調用newNode() ,賦值給tab第i個;
- 如果不為空,可能存在2種情況:hash值重復了,也就是put過程中,發現之前已經有了此key對應的value,則暫時e = p;
至於另外一種情況就是位置沖突了,即根據(n - 1) & hash算法發生了碰撞,再次分情況討論;
1.以鏈表的形式存入;
2.如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹; - 最后,如果e不為空,將e添加到table中(e.value 被賦值為 putVal()中的參數 value);
代碼如下:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//hashmap對象中 tabel屬性為空--->第一次put---->resize()
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//發現tab[i] 沒有值,直接存入即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//tab[i]取到值了,莫慌,先定義下方2個變量
Node<K,V> e; K k;
//如果是 key 重復了 很簡單,直接e = p
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) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
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;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
get方法
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
- 還是先根據key獲取hash值,其他沒什么可說的,有值value,沒有值返回null,直接進入getNode()
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- 不難發現,此方法明顯沒有putVal復雜,並且參數比較簡單:一個int型的hash值,一個Object的key;
- 先定義幾個變量:
- 1個Node的數組 tab,兩個Node對象,first,e,一個int n,一個K k;
- 進入方法的if判斷,如果不走此if,直接返回null;
- 判斷了如下內容,並且用 && 連接(同時滿足,並且有短路)
(tab = table) != null, 只要進行過 put 操作,即滿足;(n = tab.length) > 0,要求map集合中有元素(與上一個條件不同:先put再remove,此判斷不成立);(first = tab[(n - 1) & hash]) != null,還是與put時同樣的計算索引方法,!=null 代表tab數組對應索引有元素;
- 滿足最外層的if后,再次需要分2種情況討論;
- 別找了 hash值也是first的hash值,傳入的key也是那個key(==直接返回true,重寫了 equal后 返回true也可以)
此時,直接返回first即可; - 樹中還是鏈表中?做出不同處理
1.紅黑樹:直接調用getTreeNode(),不做深究
2.鏈表:通過.next() 循環獲取,知道找到滿足條件的key為止
- 別找了 hash值也是first的hash值,傳入的key也是那個key(==直接返回true,重寫了 equal后 返回true也可以)
- 最后,可以返回之前定義的 Node對象 e啦。
再來兩張圖,加深理解
-
從結構實現來講,HashMap是數組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下如所示。

-
HashMap的put方法執行過程可以通過下圖來理解。

本文參考:http://yikun.github.io/2015/04/01/Java-HashMap工作原理及實現/
http://www.importnew.com/20386.html
