關(guān)于golang中map使用的幾點(diǎn)注意事項(xiàng)總結(jié)(強(qiáng)烈推薦!)
前言
日常的開(kāi)發(fā)工作中,map 這個(gè)數(shù)據(jù)結(jié)構(gòu)相信大家并不陌生,在 golang 里面,當(dāng)然也有 map 這種類型
關(guān)于 map 的使用,還是有蠻多注意事項(xiàng)的,如果不清楚,這些事項(xiàng),關(guān)鍵時(shí)候可能會(huì)踩坑,我們一起來(lái)演練一下吧
1 使用 map 記得初始化
寫一個(gè) demo
- 定義一個(gè) map[int]int 類型的變量 myMap , 不做初始化
- 我們可以讀取 myMap 的值,默認(rèn)為 零值
- 但是我們往沒(méi)有初始化的 myMap 中寫入值,程序就會(huì) panic ,這里切記不要踩坑
func main(){ var myMap map[int]int fmt.Println("myMap[1] == ",myMap[1]) }
程序運(yùn)行效果:
# go run main.go
myMap[1] == 0
代碼中加入寫操作:
func main(){ var myMap map[int]int fmt.Println("myMap[1] == ",myMap[1]) myMap[1] = 10 fmt.Println("myMap[1] == ",myMap[1]) }
程序運(yùn)行效果:
# go run main.go
myMap[1] == 0
panic: assignment to entry in nil mapgoroutine 1 [running]:
main.main()
/home/admin/golang_study/later_learning/map_test/main.go:20 +0xf3
exit status 2
程序果然報(bào) panic 了,我們實(shí)際工作中需要萬(wàn)分小心,對(duì)代碼要有敬畏之心
2 map 的遍歷是無(wú)序的
定義一個(gè) map[int]int 類型的 map,并初始化 5 個(gè)數(shù)
func main() { myMap := map[int]int{ 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} for k := range myMap { fmt.Println(myMap[k]) } }
程序運(yùn)行效果:
# go run main.go
1
2
3
4
5
# go run main.go
5
1
2
3
4
# go run main.go
3
4
5
1
2
運(yùn)行上述代碼 3 次,3 次結(jié)果都不一樣,當(dāng)然,也有可能 3 次結(jié)果的順序都是一樣的
因?yàn)?GO 中的 map 是基于哈希表實(shí)現(xiàn)的,所以遍歷的時(shí)候是無(wú)序的
若我們需要清空這個(gè) map ,那么我們可以直接將對(duì)應(yīng)的 map 變量置為 nil 即可,例如
myMap = nil
3 map 也可以是二維的
map 也是可以像數(shù)組一樣是二維的,甚至是多維的都可以,主要是看我們的需求了
可是我們要注意,只是定義的時(shí)候類似二維數(shù)組,但是具體使用的時(shí)候還是有區(qū)別的
我們可以這樣來(lái)操作二維數(shù)組
func main() { myMap := map[int]map[string]string{} myMap[0] = map[string]string{ "name":"xiaomotong", "hobby":"program", } fmt.Println(myMap) }
程序運(yùn)行效果:
# go run main.go
map[0:map[name:xiaomotong hobby:program]]
我們不可以這樣來(lái)操作二維數(shù)組
func main() { myMap := map[int]map[string]string{} myMap[0]["name"] = "xiaomotong" myMap[0]["hobby"] = "program" fmt.Println(myMap) }
程序運(yùn)行效果:
# go run main.go
panic: assignment to entry in nil mapgoroutine 1 [running]:
main.main()
/home/admin/golang_study/later_learning/map_test/main.go:17 +0x7f
exit status 2
原因很簡(jiǎn)單,程序報(bào)的 panic 日志已經(jīng)說(shuō)明了原因
是因?yàn)?myMap[0] 鍵 是 0 沒(méi)問(wèn)題,但是 值是 map[string]string 類型的,需要初始化才可以做寫操作,這也是我們文章第一點(diǎn)所說(shuō)到的
要是還是想按照上面這種寫法來(lái),那也很簡(jiǎn)單,加一句初始化就好了
func main() { myMap := map[int]map[string]string{} myMap[0] = map[string]string{} myMap[0]["name"] = "xiaomotong" myMap[0]["hobby"] = "program" fmt.Println(myMap) }
4 獲取 map 的 key 最好使用這種方式
工作中,我們會(huì)存在需要獲取一個(gè) map 的所有 key 的方式,這個(gè)時(shí)候,我們一般是如何獲取的呢,接觸過(guò)反射的 xdm 肯定會(huì)說(shuō),這很簡(jiǎn)單呀,用反射一句話就搞定的事情,例如:
func main() { myMap := map[int]int{ 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} myKey := reflect.ValueOf(myMap).MapKeys() for v :=range myKey{ fmt.Println(v) } }
運(yùn)行程序go run main.go
,結(jié)果如下:
可是我們都知道,golang 中的 反射 reflect 確實(shí)寫起來(lái)很簡(jiǎn)潔,但是效率真的非常低,我們平時(shí)使用最好還是使用下面這種方式
func main() { myMap := map[int]int{ 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} myKey := make([]int,0,len(myMap)) for k :=range myMap{ myKey = append(myKey,myMap[k]) } fmt.Println(myKey) }
這種編碼方式,提前已經(jīng)設(shè)置好 myKey 切片的容量和 map 的長(zhǎng)度一致,則后續(xù)向 myKey 追加 key 的時(shí)候,就不會(huì)出現(xiàn)需要切片擴(kuò)容的情況
程序運(yùn)行效果:
# go run main.go
[2 3 4 5 1]
我們可以看到,拿出來(lái)的 key ,也不是有序的
5 map 是并發(fā)不安全的 ,sync.Map 才是安全的
最后咱們?cè)賮?lái)模擬一下和驗(yàn)證一下 golang 的 map 不是安全
模擬 map 不安全的 demo, 需要多開(kāi)一些協(xié)程才能模擬到效果,實(shí)驗(yàn)了一下,我這邊模擬開(kāi) 5 萬(wàn) 個(gè)協(xié)程
type T struct { myMap map[int]int } func (t *T) getValue(key int) int { return t.myMap[key] } func (t *T) setValue(key int, value int) { t.myMap[key] = value } func main() { ty := T{myMap: map[int]int{}} wg := sync.WaitGroup{} wg.Add(50000) for i := 0; i < 50000; i++ { go func(i int) { ty.setValue(i, i) fmt.Printf("get key == %d, value == %d \n", i, ty.getValue(i)) wg.Done() }(i) } wg.Wait() fmt.Println("program over !!") }
運(yùn)行程序變會(huì)報(bào)錯(cuò)如下信息:
# go run main.go
fatal error: concurrent map writes
...
如果硬是要使用 map 的話, 也可以加上一把互斥鎖就可以解決了
咱們只用修改上述的代碼,結(jié)構(gòu)體定義的位置,和 設(shè)置值的函數(shù)
type T struct { myMap map[int]int lock sync.RWMutex } func (t *T) setValue(key int, value int) { t.lock.Lock() defer t.lock.Unlock() t.myMap[key] = value }
為了檢查方便,我們把程序輸出的值打印到一個(gè)文件里面 go run main.go >> map.log
程序運(yùn)行后,可以看到,真實(shí)打印的 key 對(duì)應(yīng)數(shù)據(jù),確實(shí)是有 5000 行,沒(méi)毛病
通過(guò)以上例子,就可以明白 golang 中的 map,確實(shí)不是并發(fā)安全的,需要加鎖,才能做到并發(fā)安全
golang 也給我們提供了并發(fā)安全的 map ,sync.Map
sync.Map 的實(shí)現(xiàn)機(jī)制,簡(jiǎn)單來(lái)說(shuō),是他自身自帶鎖,因此可以控制并發(fā)安全
好了,今天就到這里,語(yǔ)言是好語(yǔ)言,工具也是好工具,我們需要實(shí)際用起來(lái)才能發(fā)揮他們的價(jià)值,不用的話一切都是白瞎
總結(jié)
到此這篇關(guān)于關(guān)于golang中map使用的幾點(diǎn)注意事項(xiàng)的文章就介紹到這了,更多相關(guān)golang map使用注意事項(xiàng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)的最簡(jiǎn)單數(shù)獨(dú)解法
前面給大家介紹過(guò)使用javascript實(shí)現(xiàn)的簡(jiǎn)單的數(shù)獨(dú)解法,小伙伴們都非常喜歡,今天我們?cè)賮?lái)分享一則go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)單的數(shù)獨(dú)解法,有需要的小伙伴來(lái)參考下。2015-03-03GO語(yǔ)言中的方法值和方法表達(dá)式的使用方法詳解
這篇文章主要介紹了GO的方法值和方法表達(dá)式的使用方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02Golang使用反射的動(dòng)態(tài)方法調(diào)用詳解
Go是一種靜態(tài)類型的語(yǔ)言,提供了大量的安全性和性能。這篇文章主要和大家介紹一下Golang使用反射的動(dòng)態(tài)方法調(diào)用,感興趣的小伙伴可以了解一下2023-03-03Go 請(qǐng)求兔子識(shí)別接口實(shí)現(xiàn)流程示例詳解
這篇文章主要為大家介紹了Go 請(qǐng)求兔子識(shí)別接口實(shí)現(xiàn)流程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04ubuntu安裝golang并設(shè)置goproxy的方法步驟
在Ubuntu系統(tǒng)上安裝Go語(yǔ)言(Golang)有多種方法,包括使用包管理器、從源代碼編譯安裝以及使用版本管理工具如gvm,安裝完成后,為了方便管理Go語(yǔ)言項(xiàng)目依賴,需要設(shè)置GOPATH環(huán)境變量并配置Go代理,本文介紹ubuntu安裝golang并設(shè)置goproxy的方法,感興趣的朋友一起看看吧2024-10-10通過(guò)手機(jī)案例理解Go設(shè)計(jì)模式之裝飾器模式的功能屬性
這篇文章主要為大家介紹了Go設(shè)計(jì)模式之裝飾器模式的功能屬性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05