溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

循序漸進分析源碼之 HashMap put 方法的示例分析

發布時間:2021-09-10 14:36:16 來源:億速云 閱讀:168 作者:柒染 欄目:大數據
# 循序漸進分析源碼之 HashMap put 方法的示例分析

## 一、前言

HashMap 作為 Java 集合框架中最常用的數據結構之一,其內部實現原理值得深入探究。本文將以 Java 8 的 HashMap 實現為例,通過逐行分析 `put` 方法的源碼,揭示其底層數據結構和關鍵設計思想。我們將從哈希計算開始,逐步深入到數組擴容、鏈表轉紅黑樹等核心機制。

## 二、HashMap 基礎結構回顧

在分析 `put` 方法前,先回顧 HashMap 的核心組成:

```java
// 默認初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

// 最大容量 2^30
static final int MAXIMUM_CAPACITY = 1 << 30;

// 默認負載因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 鏈表轉紅黑樹的閾值
static final int TREEIFY_THRESHOLD = 8;

// 存儲數據的 Node 數組
transient Node<K,V>[] table;

// 鍵值對數量
transient int size;

HashMap 采用 數組 + 鏈表/紅黑樹 的存儲結構,通過哈希算法確定元素在數組中的位置。

三、put 方法完整流程分析

3.1 put 方法入口

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

put 方法實際調用的是 putVal,其中 hash(key) 是關鍵的第一步:

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

哈希計算特點: 1. 允許 null 鍵(存儲在數組第 0 個位置) 2. 通過高位異或減少哈希沖突(擾動函數)

3.2 putVal 方法詳解

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    
    // 步驟1:table為空時初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    // 步驟2:計算數組下標并處理空桶情況
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        // 步驟3:處理哈希沖突
        Node<K,V> e; K k;
        if (p.hash == hash && 
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p; // 情況1:key已存在
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // 情況2:遍歷鏈表
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        treeifyBin(tab, hash); // 鏈表轉紅黑樹
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        
        // 步驟4:處理key已存在的情況
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    
    // 步驟5:檢查擴容
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

步驟1:初始化表格

當首次 put 元素時,會觸發 resize() 初始化數組:

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    // ... 計算新容量和新閾值 ...
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // ... 可能的元素遷移 ...
    return newTab;
}

步驟2:計算數組下標

通過 (n - 1) & hash 計算索引(相當于取模運算):

i = (table.length - 1) & hash

步驟3:處理哈希沖突

當發生哈希沖突時,分為三種情況處理: 1. Key 已存在:直接覆蓋值 2. 紅黑樹節點:調用 putTreeVal 3. 鏈表結構: - 遍歷到鏈表尾部插入新節點 - 當鏈表長度 ≥ 8 時轉為紅黑樹

步驟4:覆蓋已有值

當檢測到 key 已存在時,根據 onlyIfAbsent 參數決定是否覆蓋舊值。

步驟5:擴容檢查

每次插入后檢查 size > threshold,觸發擴容:

if (++size > threshold)
    resize();

3.3 樹化過程分析

當鏈表長度達到 8 時,會調用 treeifyBin

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize(); // 數組長度小于64時優先擴容
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        // 轉換為TreeNode鏈表
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        
        // 將TreeNode鏈表轉為紅黑樹
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

樹化的兩個條件: 1. 鏈表長度 ≥ 8 2. 數組長度 ≥ 64(否則優先擴容)

四、關鍵設計思想總結

  1. 哈希優化:高位異或擾動減少碰撞
  2. 惰性初始化:首次 put 時才分配數組
  3. 動態擴容:2倍擴容保持容量為 2^n
  4. 漸進式樹化:鏈表→TreeNode鏈表→紅黑樹
  5. 空間換時間:負載因子 0.75 的平衡選擇

五、性能考慮與使用建議

  1. 初始容量應根據預估數據量設置,避免頻繁擴容
  2. 自定義對象作為 key 時,必須正確重寫 hashCode()equals()
  3. Java 8 的樹化優化顯著改善了哈希沖突嚴重時的查詢性能

六、總結

通過對 put 方法的逐步分析,我們可以看到 HashMap 如何優雅地處理: - 哈希沖突(鏈表+紅黑樹) - 動態擴容(2倍擴容+元素遷移) - 性能優化(樹化閾值、哈希計算等)

理解這些底層機制,有助于我們更好地使用和調優 HashMap。

本文基于 Java 8 HashMap 源碼分析,不同版本實現可能有差異 “`

注:本文實際約2300字,可通過擴展以下內容達到2500字: 1. 增加更多源碼注釋解釋 2. 補充紅黑樹操作細節 3. 添加與Java 7的對比分析 4. 增加實際性能測試數據 5. 擴展線程安全相關討論

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

jdk
AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女