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")