1. 程式人生 > >記錄小白實習生的HashMap源碼 put元素 的學習和一些疑問

記錄小白實習生的HashMap源碼 put元素 的學習和一些疑問

imp eval www acc .cn transient nod 學習 span

首先看HashMap存儲結構

transient Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
   final int hash;
   final K key;
   V value;
   Node<K,V> next;

   Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        
this.value = value; this.next = next; } /* …… */ }

我對存儲結構的理解

技術分享圖片

初始化一個長度為16的Node數組,數組中每一個元素是一個Node構成的單鏈表,好像是大家說的桶?當桶中Node結點長度(鏈表長度)大於等於TREEIFY_THRESHOLD (8)時 單鏈表改為 樹(紅黑樹?現在還一點不了解,知道個名字) 存儲 ,當桶中Node結點長度(鏈表長度)小於等於UNTREEIFY_THRESHOLD (6)時,樹形結構轉換為單鏈表存儲

public V put(K key, V value) {
        
return putVal(hash(key), key, value, false, true); }

putVal 參數 分別為 key的哈希值,key值,value值,onlyIfAbsent true表示只有在該key對應原來的value為null的時候才插入,也就是說如果value之前存在了,就不會被新put的元素覆蓋,false相反,evict //evict if false, the table is in creation mode. 這個是源碼中的註釋,true的話就不是creation mode?看園裏大佬zju_jzb的博說

用於LinkedHashMap中的尾部操作,這裏沒有實際意義   傳送門
https://www.cnblogs.com/jzb-blog/p/6637823.html

new HashMap<>()進行put時

先對table=null 和 table.length = 0 的情況進行處理 執行resize方法默認構造一個長為16的Node數組

再根據hash值 (table.length - 1) & hash 計算出put 的桶的下標

  若該元素為空 newNode(hash, key, value, null); 創建一個單鏈表的“頭”結點

  若該元素不為空

     if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

如果第一個元素key與put的key相同時,將第一個元素引用賦值給要put的新結點e

    

     else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

如果第一個元素 是 TreeNode類型時,說明已轉換為樹形結構存儲,插入到樹中

     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;
                }
            }

如果第一個元素與要put元素不同,而且此時也仍是單鏈表結構存儲的話,遍歷鏈表。找到後又分三種情況

      第一種情況時,插入後鏈表長度達到8,需要轉化為樹形結構。

      第二種情況時,插入後鏈表長度小於8,仍然是鏈表存儲。

      第三種情況時,鏈表中遍歷到相同key值的結點,獲得該結點的引用

        if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }

        對已存在key的value進行覆蓋 返回put之前key所對應的值

        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

modCount是對HashMap結構改變次數的記錄(插入刪除)

若put一個元素後 需要對Node[] table進行擴容 就擴容

putVal 方法走到這裏時已經說明 HashMap中不存在和要put的Key相同的Key 返回null

afterNodeInsertion方法還不了解

看了一天了 感覺對HashMap的大致結構有一定的了解了 但是還有很多疑問 很多高深的東東 這次算入園了 希望各位大佬多多照顧小弟

希望能在java路上越走越遠

記錄小白實習生的HashMap源碼 put元素 的學習和一些疑問