golang map的底層結構

若-飞發表於2024-11-28

1. Map 的主要結構

map 的底層資料結構定義在 Go 原始碼的 runtime 包中,其核心結構體是 hmap。Go 的 map 使用 雜湊表 儲存鍵值對,並結合了**桶(bucket)**機制來最佳化儲存和查詢。

hmap 的主要欄位

  • count:儲存的鍵值對數量。
  • buckets:雜湊桶的陣列,儲存鍵值對的實際資料。
  • hash0:雜湊種子,用於隨機化雜湊函式,防止雜湊衝突攻擊。
  • Bbuckets 的對數大小(2^B 表示桶的數量)。
  • overflow:溢位桶,用於解決雜湊衝突導致的桶空間不足。

bucket 的結構

每個桶中包含:

  • 一個定長陣列用於儲存鍵。
  • 一個定長陣列用於儲存值。
  • 一個附加的連結串列指標(溢位桶),用於儲存衝突過多時溢位的資料。

2. Map 的底層操作

插入(Set)

  1. 計算雜湊值:對鍵呼叫雜湊函式,結合 hash0 生成雜湊值。
  2. 定位桶:根據雜湊值計算目標桶的位置。
  3. 儲存鍵值對
    • 如果目標桶有空閒空間,直接插入。
    • 如果目標桶已滿,則建立溢位桶,將新鍵值對存入溢位桶。

查詢(Get)

  1. 計算雜湊值:與插入操作相同。
  2. 定位桶:找到目標桶後,逐個檢查桶內的鍵是否與目標鍵匹配。
  3. 返回值:如果找到匹配的鍵,則返回值;否則,繼續查詢溢位桶,直到找到或確定不存在。

刪除(Delete)

  1. 計算雜湊值:定位目標桶。
  2. 查詢鍵:遍歷桶及其溢位桶,找到匹配的鍵。
  3. 移除鍵值對:將鍵值對標記為空位,或者重組桶內資料。

3. 解決雜湊衝突

Go 的 map 使用鏈地址法(chaining with linked lists)來解決雜湊衝突:

  • 當多個鍵對映到同一個桶時,這些鍵值對會儲存在溢位桶中。
  • 溢位桶以連結串列的形式連線在主桶之後。

4. 擴容機制

map 的鍵值對數量增長到一定程度時,Go 會觸發擴容

  • 觸發條件:當儲存負載超過一定閾值(通常是 6.5)時。
  • 擴容過程
    1. 分配新的桶陣列,其大小是原來的兩倍。
    2. 重新雜湊所有鍵,分配到新的桶中。
    3. 新桶可以減少衝突,提高訪問效率。

5. Map 的特點

  • 無序:由於鍵值對的儲存位置依賴雜湊值,map 的迭代順序是不確定的。
  • 高效:平均情況下,map 的查詢、插入、刪除操作的時間複雜度為 O(1)
  • 自動擴容:Go 的 map 會根據鍵值對數量動態擴容,使用者無需手動調整。

6. Map 的優勢與限制

優勢

  1. 快速訪問:雜湊表的結構使得查詢、插入、刪除操作非常高效。
  2. 動態擴容:能適應不斷變化的資料規模。
  3. 簡單易用:提供友好的語法,使用者無需關心底層細節。

限制

  1. 鍵必須可比較:鍵型別必須支援 ==!= 操作(如 slice 不可用作鍵)。
  2. 無序性:無法保證鍵值對的迭代順序。
  3. 高負載下效能可能下降:如果雜湊衝突嚴重或擴容頻繁,效能會受影響。

7. 簡單示例

package main

import "fmt"

func main() {
    myMap := make(map[string]int)
    myMap["Alice"] = 25
    myMap["Bob"] = 30

    fmt.Println("Alice's age:", myMap["Alice"])
    delete(myMap, "Bob")
    fmt.Println("After deletion:", myMap)
}

總結

Go 的 map 是基於雜湊表實現的,結合了桶(bucket)和溢位連結串列來處理雜湊衝突,並透過動態擴容保持效能的穩定。它是 Go 程式中處理鍵值對的高效工具。

相關文章