天天看點

嘗試引用非結構體數組的字段_Go Map源碼解讀之資料結構

點選上方藍色“後端開發雜談”關注我們, 專注于後端日常開發技術分享

map 資料結構與實際的資料結構

map 中的資料被存放在一個數組中的, 數組的元素是桶(bucket), 每個桶至多包含8個鍵值對資料. 哈希值低位(low-order bits)用于選擇桶, 哈希值高位(high-order bits)用于在一個獨立的桶中差別出鍵. 哈希值高低位示意圖如下:

嘗試引用非結構體數組的字段_Go Map源碼解讀之資料結構

image 源碼位于

src/runtime/map.go

.

結構體

// A header for a Go map.
type hmap struct {
    count     int    // 代表哈希表中的元素個數, 調用len(map)時, 傳回的就是該字段值.
    flags     uint8  // 狀态标志, 下文常量中會解釋四種狀态位含義.
    B         uint8  // buckets(桶)的對數log_2(哈希表元素數量最大可達到裝載因子*2^B)
    noverflow uint16 // 溢出桶的大概數量.
    hash0     uint32 // 哈希種子.

    buckets    unsafe.Pointer // 指向buckets數組的指針, 數組大小為2^B, 如果元素個數為0, 它為nil.
    oldbuckets unsafe.Pointer // 如果發生擴容, oldbuckets是指向老的buckets數組的指針, 老的buckets數組大小是新
                              // 的buckets的1/2.非擴容狀态下, 它為nil. 它是判斷是否處于擴容狀态的辨別

    nevacuate  uintptr        // 表示擴容進度, 小于此位址的buckets代表已搬遷完成.

    extra *mapextra // 這個字段是為了優化GC掃描而設計的. 當key和value均不包含指針, 并且都可以inline時使用.
                    // extra是指向mapextra類型的指針.
}

// mapextra holds fields that are not present on all maps.
type mapextra struct {
    // 就使用 hmap 的 extra 字段來存儲 overflow buckets, 

    // 如果 key 和 value 都不包含指針, 并且可以被 inline(<=128 位元組), 則将 bucket type 标記為不包含指針 (使用
    // ptrdata 字段, 為0表示不包含指針). 這樣可以避免 GC 掃描整個 map. 但是 bmap.overflow 是一個指針. 這時候我
    // 們隻能把這些 overflow 的指針都放在 hmap.extra.overflow 和 hmap.extra.oldoverflow 中了.
    //  
    // 當 key 和 elem 不包含指針時, 才使用 overflow 和 oldoverflow. 
    // overflow 包含的是 hmap.buckets 的 overflow bucket, 
    // oldoverflow 包含擴容時的 hmap.oldbuckets 的 overflow bucket.
    overflow    *[]*bmap
    oldoverflow *[]*bmap

    // 指向空閑的 overflow bucket 的指針(第一個空閑的bucket位址)
    nextOverflow *bmap
}

// A bucket for a Go map.
type bmap struct {
    // tophash包含此桶中每個鍵的哈希值最高位元組(高8位)資訊(也就是前面所述的high-order bits).
    // 如果tophash[0] 
    tophash [bucketCnt]uint8
}
           

常量值

bmap(即map當中的bucket)記憶體結構

hmap (即map) 記憶體結構

bmap 也就是 bucket(桶)的記憶體模型圖解如下(代碼邏輯就是上述的

bmap

函數).

嘗試引用非結構體數組的字段_Go Map源碼解讀之資料結構

image

該桶的第7,8位cell還未有對應的鍵值對. 注意: key和value是各自存儲起來的, 并非想象中的 key/value/key/value…的形式. 這樣的做法好處在于消key/value之間的padding, 例如

map[int64]int

. 還有,在8個鍵值對資料後面還有一個overflow指針, 因為桶中最多隻能裝8個鍵值對, 如果有多餘的鍵值對落到目前桶, 那麼就需要再建構一個桶(溢出桶), 通過 overflow 指針連結起來.

最後, 這裡展示一個 B=4 的完整 map 結構:

嘗試引用非結構體數組的字段_Go Map源碼解讀之資料結構

image

  • 參考連結

    1.8 萬字詳解 Go 是如何設計 Map 的

嘗試引用非結構體數組的字段_Go Map源碼解讀之資料結構