按部就班分析源碼 - HashMap put 方法的執行流程(jdk 1.7 )

開篇

本篇博客主要分析 jdk1.7 中的 HashMap 的 put() 方法。接下來是幾點說明:算法

  • 經過畫流程圖的方式分析方法的執行流程,不會細緻到具體每一個方法,好比 hash 算法。
  • 不講 HashMap 的相關概念以及使用方法, 可能只會提一下。
  • 文章貼出的代碼註釋很重要。
  • 具體細節以後會慢慢補充, 按部就班。

進入正題

預備知識

jdk1.7 中 HashMap 採用的是數組 + 鏈表的數據結構(ps:若是數組和鏈表是什麼東西,不建議往下看),以下圖所示:數組

jdk1.7 中的HashMap的數據結構

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

put 方法的源代碼(無註釋)

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

put 方法的源代碼(有註釋)

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

流程圖

這裏咱們就看下完整的流程圖是什麼樣的。

jdk1.7 HashMap 的 put 方法執行流程圖

總結

上面不少細節都沒講, 好比:hash 算法是怎麼實現的,爲何? HashMap 的擴容機制,擴容因子爲何是 0.75,爲何是兩倍擴容,甚至其餘更難的點,HashMap 爲何是非線程安全的等等。 上面這些問題是我學習 HashMap 源碼時遇到的問題,基本看到一個方法就想點進去看細節,最後發現沒頭沒尾的。而本文只是先描述下 put 方法的大體執行流程,以後會逐個擊破上面提到的問題, 按部就班。

相關文章
相關標籤/搜索