本篇博客主要分析 jdk1.7 中的 HashMap 的 put() 方法。接下來是幾點說明:算法
jdk1.7 中 HashMap 採用的是數組 + 鏈表的數據結構(ps:若是數組和鏈表是什麼東西,不建議往下看),以下圖所示:數組
當 put 一個元素時, 會先計算出元素在數組中的所在位置, 若是那個位置已經有元素存在, 這時就發生了哈希衝突(或叫哈希碰撞)。至於發生哈希衝突後, 稍後詳解。安全
注意: 這張圖很重要, 下面會反覆提到數據結構
HashMap 源碼中的數組和鏈表如何定義的?學習
鏈表中的節點定義爲 Entry<K,V>, 詳情能夠看下面的代碼段(最好結合上面的圖)。this
// 鏈表中的節點 static class Entry<K,V> implements Map.Entry<K,V> { final K key; // 存放 鍵 V value; // 存放 值 Entry<K,V> next;// 指向鏈表中的下一個節點 int hash;// 節點對應的hash值 // 此處省略 set/get 等方法 } // 數組 : 默認爲空數組 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
這裏插一句:線程
好比上面的最後一行代碼,裏面有一個 transient 關鍵字, 它的的用法和含義咱們暫時不用瞭解,着急的話請自行學習,後續的文章可能也會補充,本文咱們只講流程,它並不會妨礙咱們。 說這句話是由於我以前看的時候,就想把每一處都瞭解清楚,從而致使耗費了不少時間,最後發現腦子裏一團漿糊, 這裏再次強調 按部就班。code
由數組和鏈表的定義,咱們能得出什麼?blog
數組中能夠存放一個 Entry,也能夠存放一個 Entry 鏈。(結合上面的圖)索引
HashMap 中的哈希算法?
把 put 方法傳入的 key,通過算法計算,獲得一個 int 類型的數字並返回。
key 爲 null 的元素放在哪?
數組(table[])中索引爲 0 的位置, (再次結合上面的圖)
這裏說幾個 put 方法中用到的幾個變量。
// 一個大小爲0 的空數組 static final Entry<?,?>[] EMPTY_TABLE = {}; // 存放元素的數組,默認指向上面的空數組 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
public V put(K key, V value) { // 若是容器爲空, 就初始化容器 if (table == EMPTY_TABLE) { inflateTable(threshold); } // 若是 key 爲null,放置在第 0 個 桶的位置 if (key == null) return putForNullKey(value); // 根據 key 計算 hash值 int hash = hash(key); // 根據 hash 值計算出應該存放到數組中的哪一個位置(暫時命名爲位置 A) int i = indexFor(hash, table.length); // 判斷位置 A 是否已經被佔據,若是是,就遍歷該位置上的鏈表 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 若是鏈表中知足如下條件(命名爲條件1)的 Entry ,就覆蓋掉它 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++;// 略過,不作分析 // 若是位置 A上是空的 // 或者位置 A 中的鏈表不存在知足條件1的 Entry,就建立一個Entry 放置在位置A, 充當鏈表表頭。 addEntry(hash, key, value, i); return null; }
這裏咱們就看下完整的流程圖是什麼樣的。
上面不少細節都沒講, 好比:hash 算法是怎麼實現的,爲何? HashMap 的擴容機制,擴容因子爲何是 0.75,爲何是兩倍擴容,甚至其餘更難的點,HashMap 爲何是非線程安全的等等。 上面這些問題是我學習 HashMap 源碼時遇到的問題,基本看到一個方法就想點進去看細節,最後發現沒頭沒尾的。而本文只是先描述下 put 方法的大體執行流程,以後會逐個擊破上面提到的問題, 按部就班。