HashMap的put方法的具體流程


先上原文地址:https://thinkwon.blog.csdn.net/article/details/104588551

摘抄部分如下:

當我們put的時候,首先計算 keyhash值,這里調用了 hash方法,hash方法實際是讓key.hashCode()key.hashCode()>>>16進行異或操作,高16bit補0,一個數和0異或不變,所以 hash 函數大概的作用就是:高16bit不變,低16bit和高16bit做了一個異或,目的是減少碰撞。按照函數注釋,因為bucket數組大小是2的冪,計算下標index = (table.length - 1) & hash,如果不做 hash 處理,相當於散列生效的只有幾個低 bit 位,為了減少散列的碰撞,設計者綜合考慮了速度、作用、質量之后,使用高16bit和低16bit異或來簡單處理減少碰撞,而且JDK8中用了復雜度 O(logn)的樹結構來提升碰撞下的性能。

putVal方法執行流程圖

putVal方法執行流程圖

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

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//實現Map.put和相關方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 步驟①:tab為空則創建 
    // table未初始化或者長度為0,進行擴容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 步驟②:計算index,並對null做處理  
    // (n - 1) & hash 確定元素存放在哪個桶中,桶為空,新生成結點放入桶中(此時,這個結點是放在數組中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已經存在元素
    else {
        Node<K,V> e; K k;
        // 步驟③:節點key存在,直接覆蓋value 
        // 比較桶中第一個元素(數組中的結點)的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 將第一個元素賦值給e,用e來記錄
                e = p;
        // 步驟④:判斷該鏈為紅黑樹 
        // hash值不相等,即key不相等;為紅黑樹結點
        // 如果當前元素類型為TreeNode,表示為紅黑樹,putTreeVal返回待存放的node, e可能為null
        else if (p instanceof TreeNode)
            // 放入樹中
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        // 步驟⑤:該鏈為鏈表 
        // 為鏈表結點
        else {
            // 在鏈表最末插入結點
            for (int binCount = 0; ; ++binCount) {
                // 到達鏈表的尾部
                
                //判斷該鏈表尾部指針是不是空的
                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值與插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出循環
                    break;
                // 用於遍歷桶中的鏈表,與前面的e = p.next組合,可以遍歷鏈表
                p = e;
            }
        }
        //判斷當前的key已經存在的情況下,再來一個相同的hash值、key值時,返回新來的value這個值
        if (e != null) { 
            // 記錄e的value
            V oldValue = e.value;
            // onlyIfAbsent為false或者舊值為null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替換舊值
                e.value = value;
            // 訪問后回調
            afterNodeAccess(e);
            // 返回舊值
            return oldValue;
        }
    }
    // 結構性修改
    ++modCount;
    // 步驟⑥:超過最大容量就擴容 
    // 實際大小大於閾值則擴容
    if (++size > threshold)
        resize();
    // 插入后回調
    afterNodeInsertion(evict);
    return null;
}

①.判斷鍵值對數組table[i]是否為空或為null,否則執行resize()進行擴容;

②.根據鍵值key計算hash值得到插入的數組索引i,如果table[i]==null,直接新建節點添加,轉向⑥,如果table[i]不為空,轉向③;

③.判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這里的相同指的是hashCode以及equals;

④.判斷table[i] 是否為treeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向⑤;

⑤.遍歷table[i],判斷鏈表長度是否大於8,大於8的話把鏈表轉換為紅黑樹,在紅黑樹中執行插入操作,否則進行鏈表的插入操作;遍歷過程中若發現key已經存在直接覆蓋value即可;

⑥.插入成功后,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM