1. Map 的主要結構
map
的底層資料結構定義在 Go 原始碼的 runtime
包中,其核心結構體是 hmap
。Go 的 map
使用 雜湊表 儲存鍵值對,並結合了**桶(bucket)**機制來最佳化儲存和查詢。
hmap
的主要欄位
count
:儲存的鍵值對數量。buckets
:雜湊桶的陣列,儲存鍵值對的實際資料。hash0
:雜湊種子,用於隨機化雜湊函式,防止雜湊衝突攻擊。B
:buckets
的對數大小(2^B
表示桶的數量)。overflow
:溢位桶,用於解決雜湊衝突導致的桶空間不足。
bucket
的結構
每個桶中包含:
- 一個定長陣列用於儲存鍵。
- 一個定長陣列用於儲存值。
- 一個附加的連結串列指標(溢位桶),用於儲存衝突過多時溢位的資料。
2. Map 的底層操作
插入(Set)
- 計算雜湊值:對鍵呼叫雜湊函式,結合
hash0
生成雜湊值。 - 定位桶:根據雜湊值計算目標桶的位置。
- 儲存鍵值對:
- 如果目標桶有空閒空間,直接插入。
- 如果目標桶已滿,則建立溢位桶,將新鍵值對存入溢位桶。
查詢(Get)
- 計算雜湊值:與插入操作相同。
- 定位桶:找到目標桶後,逐個檢查桶內的鍵是否與目標鍵匹配。
- 返回值:如果找到匹配的鍵,則返回值;否則,繼續查詢溢位桶,直到找到或確定不存在。
刪除(Delete)
- 計算雜湊值:定位目標桶。
- 查詢鍵:遍歷桶及其溢位桶,找到匹配的鍵。
- 移除鍵值對:將鍵值對標記為空位,或者重組桶內資料。
3. 解決雜湊衝突
Go 的 map
使用鏈地址法(chaining with linked lists)來解決雜湊衝突:
- 當多個鍵對映到同一個桶時,這些鍵值對會儲存在溢位桶中。
- 溢位桶以連結串列的形式連線在主桶之後。
4. 擴容機制
當 map
的鍵值對數量增長到一定程度時,Go 會觸發擴容:
- 觸發條件:當儲存負載超過一定閾值(通常是
6.5
)時。 - 擴容過程:
- 分配新的桶陣列,其大小是原來的兩倍。
- 重新雜湊所有鍵,分配到新的桶中。
- 新桶可以減少衝突,提高訪問效率。
5. Map 的特點
- 無序:由於鍵值對的儲存位置依賴雜湊值,
map
的迭代順序是不確定的。 - 高效:平均情況下,
map
的查詢、插入、刪除操作的時間複雜度為 O(1)。 - 自動擴容:Go 的
map
會根據鍵值對數量動態擴容,使用者無需手動調整。
6. Map 的優勢與限制
優勢
- 快速訪問:雜湊表的結構使得查詢、插入、刪除操作非常高效。
- 動態擴容:能適應不斷變化的資料規模。
- 簡單易用:提供友好的語法,使用者無需關心底層細節。
限制
- 鍵必須可比較:鍵型別必須支援
==
和!=
操作(如slice
不可用作鍵)。 - 無序性:無法保證鍵值對的迭代順序。
- 高負載下效能可能下降:如果雜湊衝突嚴重或擴容頻繁,效能會受影響。
7. 簡單示例
總結
Go 的 map
是基於雜湊表實現的,結合了桶(bucket)和溢位連結串列來處理雜湊衝突,並透過動態擴容保持效能的穩定。它是 Go 程式中處理鍵值對的高效工具。