map
字典(map)它能存儲(chǔ)的不是單一值的集合喘落,而是鍵值對(duì)的集合茵宪。
什么是鍵值對(duì)?它是從英文 key-value pair 直譯過來的一個(gè)詞瘦棋。顧名思義稀火,一個(gè)鍵值對(duì)就代表
了一對(duì)鍵和值。一個(gè)“鍵”和一個(gè)“值”分別代表了一個(gè)從屬于某一類型的獨(dú)立值兽狭,把它們兩個(gè)捆綁在一
起就是一個(gè)鍵值對(duì)了憾股。在 Go 語言規(guī)范中鹿蜀,應(yīng)該是為了避免歧義箕慧,他們將鍵值對(duì)換了一種稱呼,
叫做:“鍵 - 元素對(duì)”茴恰。
Go 語言的字典類型其實(shí)是一個(gè)哈希表(hash table)的特定實(shí)現(xiàn)颠焦。在這個(gè)實(shí)現(xiàn)中,鍵和元素的
最大不同在于往枣,鍵 的類型是受限的伐庭,而元素對(duì)卻可以是任意類型的。
代碼:
ma := make(map[string]string)
ma["key"] = "value"http:// 存儲(chǔ)
value, ok := ma["key"]//獲取值分冈。ok:獲取狀態(tài)圾另,value:ok=true說明有值,否則反之
fmt.Println(value, ok)
//遍歷
for key, value := range ma {
fmt.Printf("key : %s, value:%s \n", key, value)
}
//刪除
delete(ma, "key")
map:不是線程安全的雕沉。在同一時(shí)間段內(nèi)集乔,讓不同 goroutine 中的代碼,對(duì)同一個(gè)字典進(jìn)行讀寫操作是不安全
的坡椒。字典值本身可能會(huì)因這些操作而產(chǎn)生混亂扰路,相關(guān)的程序也可能會(huì)因此發(fā)生不可預(yù)知的問題。
sync.Map
在 2017 年發(fā)布的 Go 1.9 中正式加入了并發(fā)安全的字典類型sync.Map倔叼。這個(gè)字典類型提供了一些常用的鍵值存取操作方法汗唱,并保證了這些操作的并發(fā)安全。同時(shí)丈攒,它的存哩罪、取、刪等操作都可以基本保證在常數(shù)時(shí)間內(nèi)執(zhí)行完畢巡验。換句話說际插,它們的算法復(fù)雜度與map類型一樣都是O(1)的。在有些時(shí)候深碱,與單純使用原生map和互斥鎖的方案相比腹鹉,使用sync.Map可以顯著地減少鎖的爭(zhēng)用。sync.Map本身雖然也用到了鎖敷硅,但是功咒,它其實(shí)在盡可能地避免使用鎖愉阎。
代碼:
var ma sync.Map// 該類型是開箱即用,只需要聲明既可
ma.Store("key", "value") // 存儲(chǔ)值
ma.Delete("key") //刪除值
ma.LoadOrStore("key", "value")// 獲取值力奋,如果沒有則存儲(chǔ)
fmt.Println(ma.Load("key"))//獲取值
//遍歷
ma.Range(func(key, value interface{}) bool {
fmt.Printf("key:%s ,value:%s \n", key, value)
//如果返回:false榜旦,則退出循環(huán),
return true
})
擴(kuò)展:
為什么map的鍵會(huì)有限制景殷?
Go 語言規(guī)范規(guī)定溅呢,在鍵類型的值之間必須可以施加操作符==和!=。換句話說猿挚,鍵類型的值必須要支持判等操作咐旧。由于函數(shù)類型、字典類型和切片類型的值并不支持判等操作绩蜻,所以字典的鍵
類型不能是這些類型铣墨。
map映射過程:
哈希表中查找與某個(gè)鍵值對(duì)應(yīng)的那個(gè)元素值,那么我們需要先把鍵值作為參數(shù)傳給這個(gè)哈希表办绝。哈希表會(huì)先用哈希函數(shù)(hash function)把鍵值轉(zhuǎn)換為哈希值伊约。哈希值通常是一個(gè)無符號(hào)的整數(shù)。一個(gè)哈希表會(huì)持有一定數(shù)量的桶(bucket)孕蝉,也可稱之為哈希桶屡律,這些哈希桶會(huì)均勻地儲(chǔ)存其所屬哈希表收納的那些鍵 - 元素對(duì)。因此降淮,哈希表會(huì)先用這個(gè)鍵的哈希值的低幾位去定位到一個(gè)哈希桶超埋,然后再去這個(gè)哈希桶中,查找這個(gè)鍵骤肛。由于鍵 - 元素對(duì)總是被捆綁在一起存儲(chǔ)的纳本,所以一旦找到了鍵,就一定能找到對(duì)應(yīng)的元素值腋颠。隨后繁成,哈希表就會(huì)把相應(yīng)的元素值作為結(jié)果返回。只要這個(gè)鍵 - 元素對(duì)存在于哈希表中就一定會(huì)被查找到淑玫。
如何判斷那些類型作為字典的鍵比較合適?
在map查找的流程中巾腕,得知,“把鍵值轉(zhuǎn)換為哈希值”以及“把要查找的鍵值與哈希桶中的鍵值做對(duì)比”絮蒿, 是明顯兩個(gè)重要且比較耗時(shí)的操作尊搬。可以說:**求哈希和判等操作的速度越快土涝,對(duì)應(yīng)的類型就越適合作為鍵類型**佛寿。以求哈希的操作為例,寬度越小的類型速度通常越快。
基本類型:對(duì)于布爾類型冀泻、整數(shù)類型常侣、浮點(diǎn)數(shù)類型、復(fù)數(shù)類型和指針類型來說都是如此弹渔。對(duì)于字符串類型胳施,由于它的寬度是不定的,所以要看它的值的具體長度肢专,長度越短求哈希越快舞肆。類型的寬度是指它的單個(gè)值需要占用的字節(jié)數(shù)。比如博杖,bool椿胯、int8和uint8類型的一個(gè)值需要占用的字節(jié)數(shù)都是1,因此這些類型的寬度就都是1欧募。
高級(jí)類型压状。對(duì)數(shù)組類型的值求哈希實(shí)際上是依次求得它的每個(gè)元素的哈希值并進(jìn)行合并,所以速度就取決于它的元素類型以及它的長度
為什么說并發(fā)安全字典在盡量避免使用鎖跟继?
我們從它的代碼來解釋
//sync.Map 包的結(jié)構(gòu)
type Map struct {
mu Mutex //鎖
/*
由read字段的類型可知,sync.Map在替換只讀字典的時(shí)候根本用不著鎖镣丑。另外舔糖,這個(gè)只讀字典
在存儲(chǔ)鍵值對(duì)的時(shí)候,還在值之上封裝了一層莺匠。它先把值轉(zhuǎn)換為了unsafe.Pointer類型的值金吗,然后再把后者封 裝,并儲(chǔ)存在其中的原生字典中趣竣。如此一來摇庙,在變更某個(gè)鍵所對(duì)應(yīng)的值的時(shí)候,就也可以使用原子操作了遥缕。
*/
read atomic.Value// 只讀字典
/*
它存儲(chǔ)鍵值對(duì)的方式與read字段中的原生字典一致卫袒,它的鍵類型也是interface{},并且同樣是把值先做 轉(zhuǎn)換和封裝后再進(jìn)行儲(chǔ)存的
*/
dirty map[interface{}]*entry//臟字典单匣。
misses int//重建的判斷條件
}
查找鍵值對(duì)的時(shí)候:會(huì)先去只讀字典中尋找夕凝,并不需要鎖定互斥鎖。只有當(dāng)確定“只讀字典中沒有户秤,但臟字典中可能會(huì)有這個(gè)鍵”的時(shí)候码秉,它才會(huì)在鎖的保護(hù)下去訪問臟字典。相對(duì)應(yīng)的鸡号,sync.Map在存儲(chǔ)鍵值對(duì)的時(shí)候转砖,只要只讀字典中已存有這個(gè)鍵,并且該鍵值對(duì)未被標(biāo)記為“已刪除”鲸伴,就會(huì)把新值存到里面并直接返回府蔗,這種情況下也不需要用到鎖莉兰。否則,它才會(huì)在鎖的保護(hù)下把鍵值對(duì)存儲(chǔ)到臟字典中礁竞。這個(gè)時(shí)候糖荒,該鍵值對(duì)的“已刪除”標(biāo)記會(huì)被
抹去。
當(dāng)一個(gè)鍵值對(duì)應(yīng)該被刪除模捂,但卻仍然存在于只讀字典中的時(shí)候捶朵,才會(huì)被用標(biāo)記為“已刪除”的方式進(jìn)行邏輯刪除,而不會(huì)直接被物理刪除狂男。
這種情況會(huì)在重建臟字典以后的一段時(shí)間內(nèi)出現(xiàn)综看。不過,過不了多久岖食,它們就會(huì)被真正刪除掉红碑。
在查找和遍歷鍵值對(duì)的時(shí)候,已被邏輯刪除的鍵值對(duì)永遠(yuǎn)會(huì)被無視泡垃。
對(duì)于刪除鍵值對(duì)析珊,sync.Map會(huì)先去檢查只讀字典中是否有對(duì)應(yīng)的鍵。如果沒有蔑穴,臟字典中可能有忠寻,那么它就會(huì)在鎖的保護(hù)下,試圖從臟字典中刪掉該鍵值對(duì)存和。最后奕剃,sync.Map會(huì)把該鍵值對(duì)中指向值的那個(gè)指針置為nil,這是另一種邏輯刪除的方式捐腿。
除此之外纵朋,還有一個(gè)細(xì)節(jié)需要注意,只讀字典和臟字典之間是會(huì)互相轉(zhuǎn)換的茄袖。在臟字典中查找鍵
值對(duì)次數(shù)足夠多的時(shí)候操软,sync.Map會(huì)把臟字典直接作為只讀字典,保存在它的read字段中绞佩,然
后把代表臟字典的dirty字段的值置為nil寺鸥。
在讀操作有很多但寫操作卻很少的情況下,并發(fā)安全字典的性能往往會(huì)更好品山。在幾個(gè)寫操作當(dāng)中胆建,新增鍵值對(duì)的操作對(duì)并發(fā)安全字典的性能影響是最大的,其次是刪除操作肘交,最后才是修改操作笆载。
如果被操作的鍵值對(duì)已經(jīng)存在于sync.Map的只讀字典中,并且沒有被邏輯刪除,那么修改它并
不會(huì)使用到鎖凉驻,對(duì)其性能的影響就會(huì)很小腻要。