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