[go]map基本使用和底層原理

一、map基本使用

map聲明數組

var m4 map[int]int  //只是聲明  沒有開闢空間
m4[1]=100 //報錯
log.Println(m4)


建立安全

//1
m3:=make(map[int]string,100) //能夠指定長度
log.Println(len(m3)) //0 鍵值對的數量

m2:=make(map[string]string) //使用默認長度
m2["你"] = "你好啊"
log.Println(m2)

//2
d2 :=map[string]int{"one":1, "tow":2} //初始化
d3 :=map[int]int{} //建立了空map


判斷值是否存在併發

只接受一個的話默認返回的是value,兩個的話有existsapp

//判斷是否存在
val,exists :=d3["tow"]  //若是不存在 返回零值 exists爲false


map遍歷ui

m5:=map[string]string{"one":"1","tow":"2","three":"3"}
for k:=range m5{ //默認是key
	log.Println(k) //one tow...
}

for k,v:=range m5{ //k-v
	log.Println(k,v) //
}



刪除this

m5:=map[string]string{"one":"1","tow":"2","three":"3"}
delete(m5, "one")




二、map和set

go沒有內置set類型,可是能夠用map很輕鬆模仿,由於map的key是惟一的線程

type StrSet struct {
	data map[string]bool
	sync.RWMutex //讀寫鎖 保證線程安全
}

func New() *StrSet {
	return &StrSet{
		data: make(map[string]bool),
	}
}

func (this *StrSet)Add(val string) {
	this.Lock()
	defer this.Unlock()
	if this.data==nil{
		this = New()
	}
	this.data[val] = true
}

func (this *StrSet)Delete(val string)  {
	this.Lock()
	defer this.Unlock()
	if this.data==nil{
		return
	}
	delete(this.data,val)
}

func (this *StrSet) IsExist(val string) bool {
	this.RLock()
	defer this.RUnlock()
	if this.data==nil{
		return false
	}

	_,ok:=this.data[val]
	return ok
}

func (this *StrSet) GetAll() (result []string)  {
	if this.data==nil{
		return
	}
	for val :=range this.data{
		result = append(result, val)
	}
	return
}

func (this *StrSet) Clear()  {
	this.Lock()
	defer this.Unlock()
	this.data = map[string]bool{}
}




func main() {
	s:=New()
	s.Add("panbin")
	s.Add("biningo")
	s.Add("a")
	s.Add("b")
	s.Add("panbin")
	log.Println(s.GetAll())
}




三、map底層結構

借鑑了以下博客。寫的很好指針

深刻Go的Map使用和實現原理 code

先來觀摩一波map底層結構,第一眼確定萬臉懵逼😂three

// A header for a Go map.
type hmap struct { 
	count     int  // 元素個數
	flags     uint8  
	B         uint8  //包含2^B個桶  指向bmap結構 用hash來散列k-v要到哪一個桶
	noverflow uint16 //溢出的桶的個數  桶的數組可能會溢出 
    hash0     uint32 //hash種子
	
    //桶數組的指針指向bmap結構
    //bucket[0]->bucket[1]->...
    buckets    unsafe.Pointer
    
	oldbuckets unsafe.Pointer //擴容的時候用於複製的buckets數組
	nevacuate  uintptr //搬遷進度(已經搬遷的buckets數量)
	extra *mapextra  //用於擴容  若是元素過多 超過了buckets數組的範圍 就要擴容
}

mapextra 用於擴容的結構體指針

type mapextra struct {
	overflow    *[]*bmap //擴容的地址
	oldoverflow *[]*bmap //用於擴容
	nextOverflow *bmap //鏈表連接的指針
}

bmap map存儲k或v的數組,

// A bucket for a Go map.
type bmap struct {
	tophash [bucketCnt]uint8
}

map的實現過程

底層一個數組arr

index = hash(key)

arr[index] = struct{xxxx}

go map的每一個arr下面存的是一個 bucket

注意,這裏的value都會轉化爲byte類型,也就是uint8類型,key是int64類型的, 每一個bucket中能夠存儲8個kv鍵值對,

hash值的高八位存儲在bucket中的tophash中,用來快速判斷key是否存在

// A bucket for a Go map.
type bmap struct {
	tophash [bucketCnt]uint8 //bucketCnt=8 [8]uint8  這個值會動態增長
}

當每一個bucket存儲的kv對到達8個以後,會經過指針指向一個新的bucket, 從而造成一個鏈表

這個指針事實上並無顯示定義,是經過指針運算進行訪問的。能夠想象成靜態鏈表類型,而且k-v對也是經過指針運算得出的,tophash只是用來檢查key是否存在

當往map中存儲一個kv對時,經過k獲取hash值,hash值的低八位和bucket數組長度取餘,定位到在數組中的那個下標,hash值的高八位存儲在bucket中的tophash中,用來快速判斷key是否存在,key和value的具體值則經過指針運算存儲,當一個bucket滿時,經過overfolw指針連接到下一個bucket。

0gehi7myel

2l83pmdsbj






四、關於併發

map不是併發安全的

要支持併發,能夠用 sync.Map,裏面都是go的原子操做

sm:=sync.Map{}
sm.Store("one","1") //無返回值
sm.Store("tow","2")
sm.LoadOrStore("3","three") //取元素 若是沒有就存進去 並返回值
sm.Delete("1") //無返回值
log.Println(sm.Load("1")) 
log.Println(sm)
相關文章
相關標籤/搜索