golang源碼分析--map

map概念

Go 語言中 map 是一種特殊的數據結構:一種元素對(pair)的無序集合,pair 的一個元素是key,對應的另一個元素是value,所以這個結構也稱爲關聯數組或字典。這是一種快速尋找值的理想結構:給定key,對應的value可以迅速定位。

map使用

初始化

func test1() {
    map1 := make(map[string]string, 5)
    map2 := make(map[string]string)
    map3 := map[string]string{}
    map4 := map[string]string{"a": "1", "b": "2", "c": "3"}
    fmt.Println(map1, map2, map3, map4)
}

刪除map中指定key:

func main(){
	mymap:=make(map[string]string)
	mymap["key"] = "value"
	fmt.Println(mymap)
	delete(mymap,"key")
	fmt.Println(mymap)
}

新增map中的key(map會自動擴容)

func main(){
	mymap:=make(map[string]string)
	mymap["key"] = "value"
	mymap["key2"] = "value2"
}

查看map中的key是否存在

func main(){
	mymap:=make(map[string]string)
	mymap["key"] = "value"
	_,ok:=mymap["key2"]
	if ok{
		fmt.Println("key exist")
	}else{
		fmt.Println("key not exist")
	}
}

map作爲函數的參數
Golang中是沒有引用傳遞的,均爲值傳遞。這意味着傳遞的是數據的拷貝。
那麼map本身是引用類型,作爲形參或返回參數的時候,傳遞的是地址的拷貝,擴容時也不會改變這個地址。(與slice形成對比,slice擴容會生成新對象,則不影響外面的值)


func changeMap(mm map[string]string){
	for i:=0;i<10;i++ {
		ss:=strconv.Itoa(i)
		mm[ss] = ss
	}
	fmt.Println(len(mm))
}

func changeArr(arr []int){
	for i:=0;i<10;i++{
		arr = append(arr,i)
	}
	fmt.Println("function")
	fmt.Println(&arr[0])
	fmt.Println(arr)
	fmt.Println("function")
}

func main(){
	mymap:=make(map[string]string)
	mymap["key"] = "value"
	fmt.Println(len(mymap))
	fmt.Println(&mymap)
	changeMap(mymap)
	fmt.Println(&mymap)
	fmt.Println(mymap)
	arr:=make([]int,5)
	arr = append(arr,1)
	fmt.Println(&arr[0])
	changeArr(arr)
	fmt.Println(arr)
	fmt.Println(&arr[0])
}

map源碼分析

map結構體代碼

在這裏插入圖片描述


// A header for a Go map.
type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # map中key的個數,len()返回使用count
	flags     uint8 //狀態標識,主要是 goroutine 寫入和擴容機制的相關狀態控制。併發讀寫的判斷條件之一就是該值
	B         uint8  // 桶數目,可以最多容納 6.5 * 2 ^ B 個元素,6.5爲裝載因子
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 桶的地址
	oldbuckets unsafe.Pointer // 舊桶的地址,用於擴容
	nevacuate  uintptr        // 搬遷進度,當map擴容的時候需要將舊桶數據遷移到新桶

	extra *mapextra // optional fields
}

type mapextra struct {
	//若map中所有的key和value都不包含指針且內聯,那麼將存儲bucket的類型標記爲不包含指針,這樣可以防止回收此類map
	//然而,bmap.overflow是一個指針,目的是保存桶溢出的活性防止被回收,,我們將指向所有溢出桶的指針存儲在hmap.extra.overflow和hmap.extra.oldoverflow中。
	//僅當所有key和value不包含指針的時候纔是用overflow和oldoverflow
	// overflow包含hmap.buckets的溢出桶。
	// oldoverflow 包含hmap.oldbuckets的溢出桶.
	//間接允許將指向切片的指針存儲在hiter中。
	overflow    *[]*bmap
	oldoverflow *[]*bmap
	// nextOverflow 擁有一個指向空閒溢出桶的指針
	nextOverflow *bmap
}

bucket 結構體代碼

在這裏插入圖片描述

// A bucket for a Go map.
type bmap struct {
//tophash通常包含此存儲桶中每個鍵的哈希值的最高字節。
//如果tophash [0] <minTopHash,則tophash [0]而是剷鬥疏散狀態。
	tophash [bucketCnt]uint8
}

1.tophash用於記錄8個key哈希值的高8位,這樣在尋找對應key的時候可以更快,不必每次都對key做全等判斷。
2.bucket並非只有一個tophash,而是後面緊跟8組kv對和一個overflow的指針,這樣才能使overflow成爲一個鏈表的結構。但是這兩個結構體並不是顯示定義的,而是直接通過指針運算進行訪問的。
3.kv的存儲形式爲key0key1key2key3…key7val1val2val3…val7,這樣做的好處是:在key和value的長度不同的時候,節省padding空間。如上面的例子,在map[int64]int8中,4個相鄰的int8可以存儲在同一個內存單元中。如果使用kv交錯存儲的話,每個int8都會被padding佔用單獨的內存單元(爲了提高尋址速度)。

map內存增長

golang的map會自動增加容量,增加原理:
每個map的底層結構是hmap,是有若干個結構爲bmap的bucket組成的數組,每個bucket可以存放若干個元素(通常是8個),那麼每個key會根據hash算法歸到同一個bucket中,當一個bucket中的元素超過8個的時候,hmap會使用extra中的overflow來擴展存儲key。
在這裏插入圖片描述
map的增長有兩種方式
1.增加桶的數目,新的爲舊桶數目的二倍
2.橫向增長一個桶後面的鏈表(golang使用鏈地址法解決hash衝突)
map的增長方式是增量增長,所以有一箇舊的一個新的


func hashGrow(t *maptype, h *hmap) {
	// 若達到了負載因子6.5*2^B,則增大桶的數目
	// 否則,說明桶的數目足夠,增大桶的指針
	bigger := uint8(1)
	// overLoadFactor報告放置在1 << B個存儲桶中的計數項目是否超過loadFactor。
	if !overLoadFactor(h.count+1, h.B) {
		bigger = 0
		h.flags |= sameSizeGrow
	}
	oldbuckets := h.buckets
	newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)

	flags := h.flags &^ (iterator | oldIterator)
	if h.flags&iterator != 0 {
		flags |= oldIterator
	}
	// commit the grow (atomic wrt gc)
	h.B += bigger
	h.flags = flags
	h.oldbuckets = oldbuckets
	h.buckets = newbuckets
	h.nevacuate = 0
	h.noverflow = 0

	if h.extra != nil && h.extra.overflow != nil {
		// Promote current overflow buckets to the old generation.
		if h.extra.oldoverflow != nil {
			throw("oldoverflow is not nil")
		}
		h.extra.oldoverflow = h.extra.overflow
		h.extra.overflow = nil
	}
	if nextOverflow != nil {
		if h.extra == nil {
			h.extra = new(mapextra)
		}
		h.extra.nextOverflow = nextOverflow
	}

	// the actual copying of the hash table data is done incrementally
	// by growWork() and evacuate().
}

參考:
https://blog.csdn.net/xiangxianghehe/article/details/78790744
https://www.jianshu.com/p/aa0d4808cbb8
https://blog.csdn.net/wade3015/article/details/100149338
https://www.jianshu.com/p/8ea1bf5058c7
https://www.jianshu.com/p/71ef92fc34d9
https://blog.csdn.net/weixin_42506905/article/details/96176336

發佈了210 篇原創文章 · 獲贊 33 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章