系列文章目錄java
上一篇咱們討論了HashMap的擴容操做, 提到擴容操做發生在table的初始化或者table大小超過threshold後,而這兩個條件的觸發基本上就發生在put
操做中。segmentfault
本篇咱們就來聊聊HashMap的put
操做。數組
本文的源碼基於 jdk8 版本.app
HashMap 實現了Map接口, 所以必需要實現put方法:函數
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); /*final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) */ }
能夠看到, put方法是有返回值的, 這裏調用了 putVal
方法, 這個方法很重要, 咱們將經過代碼註釋的方式逐行說明.性能
在這以前咱們先看該方法的參數:this
由上面的調用可知, 該值爲hash(key)
, 是key的hash值, 關於hash的概念以前已經講過了, 這裏再也不贅述.code
待存儲的鍵值對接口
這個參數用於決定待存儲的key已經存在的狀況下,要不要用新值覆蓋原有的value
, 若是爲true
, 則保留原有值, false
則覆蓋原有值, 從上面的調用看, 該值爲false
, 說明當key
值已經存在時, 會直接覆蓋原有值。get
該參數用來區分當前是不是構造模式, 咱們在講解構造函數的時候曾經提到,HashMap的第四個構造函數能夠經過已經存在的Map初始化一個HashMap, 若是爲 false
, 說明在構造模式下, 這裏咱們是用在put
函數而不是構造函數裏面, 因此爲true
。
參數解釋完了以後, 下面咱們來逐行看代碼:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 首先判斷table是不是空的 // 咱們知道, HashMap的三個構造函數中, 都不會初始Table, 所以第一次put值時, table必定是空的, 須要初始化 // table的初始化用到了resize函數, 這個咱們上一篇文章已經講過了 // 因而可知table的初始化是延遲到put操做中的 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 這裏利用 `(n-1) & hash` 方法計算 key 所對應的下標 // 若是key所對應的桶裏面沒有值, 咱們就新建一個Node放入桶裏面 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 到這裏說明目標位置桶裏已經有東西了 else { Node<K,V> e; K k; // 這裏先判斷當前待存儲的key值和已經存在的key值是否相等 // key值相等必須知足兩個條件 // 1. hash值相同 // 2. 二者 `==` 或者 `equals` 等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // key已經存在的狀況下, e保存原有的鍵值對 // 到這裏說明要保存的桶已經被佔用, 且被佔用的位置存放的key與待存儲的key值不一致 // 前面已經說過, 當鏈表長度超過8時, 會用紅黑樹存儲, 這裏就是判斷存儲桶中放的是鏈表仍是紅黑樹 else if (p instanceof TreeNode) // 紅黑樹的部分之後有機會再說吧 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //到這裏說明是鏈表存儲, 咱們須要順序遍歷鏈表 else { for (int binCount = 0; ; ++binCount) { // 若是已經找到了鏈表的尾節點了,尚未找到目標key, 則說明目標key不存在,那咱們就新建一個節點, 把它接在尾節點的後面 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 若是鏈表的長度達到了8個, 就將鏈表轉換成紅黑數以提高查找性能 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // 若是在鏈表中找到了目標key則直接退出 // 退出時e保存的是目標key的鍵值對 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 到這裏說明要麼待存儲的key存在, e保存已經存在的值 // 要麼待存儲的key不存在, 則已經新建了Node將key值插入, e的值爲Null // 若是待存儲的key值已經存在 if (e != null) { // existing mapping for key V oldValue = e.value; // 前面已經解釋過, onlyIfAbsent的意思 // 這裏是說舊值存在或者舊值爲null的狀況下, 用新值覆蓋舊值 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //這個函數只在LinkedHashMap中用到, 這裏是空函數 // 返回舊值 return oldValue; } } // 到這裏說明table中不存在待存儲的key, 而且咱們已經將新的key插入進數組了 ++modCount; // 這個暫時用不到 // 由於又插入了新值, 因此咱們得把數組大小加1, 並判斷是否須要從新擴容 if (++size > threshold) resize(); afterNodeInsertion(evict); //這個函數只在LinkedHashMap中用到, 這裏是空函數 return null; }
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
threshold
, 若超過,則擴容。TREEIFY_THRESHOLD
(默認是8)個時,會將鏈表轉換成紅黑樹以提高查找性能。(完)
查看更多系列文章:系列文章目錄