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

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

首先看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路上越走越遠