1. 程式人生 > >Go語言Map詳解

Go語言Map詳解

map(字典、雜湊表、對映)是一種使用頻率很高的資料結構,將其作為語言的內建型別,從執行時層面進行優化,可獲得更好的效能。

一、內部實現

map的原始碼結構為:

// A header for a Go map.
type hmap struct {
    // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
    // ../reflect/type.go. Don't change this structure without also changing that code!
    count     int
// # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details hash0 uint32 // hash seed buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated) extra *mapextra // optional fields }

buckets和一個oldbuckets是用來實現增量擴容的。正常情況下直接使用buckets,而oldbuckets為空。如果當前雜湊表正在擴容中,則oldbuckets不為空,並且buckets大小是oldbuckets大小的兩倍。

type mapextra struct {
    // If both key and value do not contain pointers and are inline, then we mark bucket
    // type as containing no pointers. This avoids scanning such maps.
    // However, bmap.overflow is a pointer. In order to keep overflow buckets
    // alive, we store pointers to all overflow buckets in hmap.overflow.
    // Overflow is used only if key and value do not contain pointers.
    // overflow[0] contains overflow buckets for hmap.buckets.
    // overflow[1] contains overflow buckets for hmap.oldbuckets.
    // The indirection allows to store a pointer to the slice in hiter.
    overflow [2]*[]*bmap

    // nextOverflow holds a pointer to a free overflow bucket.
    nextOverflow *bmap
}

hashmap 通過一個 bucket 陣列實現,所有元素將被 hash 到陣列中的 bucket 中,bucket 的8個 key/value 空間如果都填滿後,就會分配新的 bucket,通過 overflow 指標串聯起來。nextOverflow應用在當空間不夠時(但是又不足以觸發 rehash 邏輯)從系統中申請記憶體臨時使用的空間做緩衝。

type bmap struct {
    // tophash generally contains the top byte of the hash value
    // for each key in this bucket. If tophash[0] < minTopHash,
    // tophash[0] is a bucket evacuation state instead.
    tophash [bucketCnt]uint8
    // Followed by bucketCnt keys and then bucketCnt values.
    // NOTE: packing all the keys together and then all the values together makes the
    // code a bit more complicated than alternating key/value/key/value/... but it allows
    // us to eliminate padding which would be needed for, e.g., map[int64]int8.
    // Followed by an overflow pointer.
}

bmap 是 bucket 資料結構的部分結構。 其功能是來大致確認這個連結串列的地址。是一個空間為 8 的陣列。其值是 key 的 hash 值的高位。當傳遞進來一個 key 的時候,會做比對,然後確定這個陣列下標,這個下標,與這個 key 所儲存的連結串列頭部有關。

按key的型別採用相應的hash演算法得到key的hash值。將hash值的低位當作Hmap結構體中buckets陣列的index,找到key所在的bucket。將hash的高8位儲存在了bucket的tophash中。注意,這裡高8位不是用來當作key/value在bucket內部的offset的,而是作為一個主鍵,在查詢時對tophash陣列的每一項進行順序匹配的。先比較hash值高位與bucket的tophash[i]是否相等,如果相等則再比較bucket的第i個的key與所給的key是否相等。如果相等,則返回其對應的value,反之,在overflow buckets中按照上述方法繼續尋找。

Bucket中key/value的放置順序,是將keys放在一起,values放在一起,為什麼不將key和對應的value放在一起呢?如果那麼做,儲存結構將變成key1/value1/key2/value2… 設想如果是這樣的一個map[int64]int8,考慮到位元組對齊,會浪費很多儲存空間。

二、建立和初始化

1、使用make函式來建立並初始化map

//建立一個對映,鍵的型別是string,值的型別是int
dict:=make(map[string]int)

當使用make建立map時,自動建立且初始化了map,此時map為空。

2、使用對映字面量來建立並初始化map

dict:=map[string]string{"Red:"#da1337","Orange":"#e95a22"}

使用對映字面量,map的初始長度會根據初始化時指定的鍵值對數量來確定。

作為無序鍵值對集合,map要求key必須是支援相等運算子(==,!=)的資料型別,如:數字、字串、指標、陣列、結構體以及對應的介面;對於切片、函式、通道型別這類具有引用語義的不能作為map的key值。

空對映與nil

func main(){
    var m1 map[string]int
    m2:=map[string]int{}

    println(m1==nil,m2==nil)
}
輸出:
     true false

m1通過宣告一個未初始化的map來建立一個值為nil的map,nil對映不能儲存鍵值對,m1[“Red”]=1將會產生一個執行時錯誤。
m2建立了一個空對映,內容為空,已經初始化,與make操作相同。

三、使用對映

測試map中key值是否存在

測試一個map裡面是否存在某個key值是map的一個重要操作,這個操作允許使用者確定是否完成了某些操作或者是否map裡快取了特定的資料。
1、可以同時取值以及一個表示key是否存在的標誌。

value,exists := colors["Blue"]
if exists {
    fmt.Println("存在,value:",value)
}

2、當通過鍵值來索引,當key值不存在時也會返回一個型別對應的零值。

value := colors["Blue"]
if value!=""{
    fmt.Println("存在,value:",value)
}

map的遍歷

map是通過range來進行遍歷,range返回的不是索引和值,而是鍵值對。

for key,value :=range colors{
    fmt.Printf("key:%v,value:%v",key,value)
}

如果想把key從一個map中刪掉,直接使用內建的delete函式。

delete(colors,"Red")