go map 比較深入的使用方案
參考blog: https://blog.golang.org/go-maps-in-action
現(xiàn)在基本上所有的編程語言都有自帶的map,或者dict,主要提供一個(gè)快速的查找,插入,刪除,具備與存儲(chǔ)體量無關(guān)的O(1)的性能,并且支持key上面的唯一性,
比如java里的HashMap,python里的Dictionary,scala里的各種Map等等。
go也原生提供了一個(gè)類似的數(shù)據(jù)類型,就叫做map。首先它是個(gè)mutable的,也就是說,可以隨時(shí)對(duì)其進(jìn)行修改。其次,它不是線程安全的。所以等價(jià)于java里的HashMap。
申明和初始化
map[KeyType]ValueType
這里的KeyType代表map的key類型,一定要是 comparable 的,而ValueType可以是任意的類型,甚至包括其他的內(nèi)嵌的map
比如
var m map[string]int
這里的keyType是string,valueType就是int
map在go里是屬于reference type,也就是作為方法的型參或者返回類型的是時(shí)候,傳遞也是這個(gè)reference的地址。不是map的本體。其次,這個(gè)map在申明的時(shí)候是nil map,需要如果沒有初始化,那么就是nil
對(duì)于這個(gè)nil的map,可以對(duì)其進(jìn)行任意的取值,返回都是(nil,err),但是如果對(duì)其設(shè)置一個(gè)新的值,就會(huì)panic
A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that
所以需要先初始化,方法1:
m = make(map[string]int)
方法二:
var m map[string]int = map[string]int{"hunter":12,"tony":10}
或者初始化一個(gè)空的map
m = map[string]int{}
讀取
i := m["route"]
如果route存在,就返回那個(gè)值,如果不存在,返回0值,也就是說,根據(jù)這個(gè)value的類型,返回缺省值,比如string,就返回“”,int 就返回0
刪除
delete(m,"route")
如果route存在,刪除成功,否則什么都沒有發(fā)生
因?yàn)樽x取在不存在key的時(shí)候返回0值,為了區(qū)分是否成功,可以采用如下手段
i, ok := m["route"]
遍歷
for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}
稍微高級(jí)點(diǎn)的用法
利用0值,因?yàn)楫?dāng)從map中讀取一個(gè)不存在的key的時(shí)候,返回0值,有時(shí)候很麻煩,有時(shí)候也可以很巧妙的利用起來,參考原文英文中的例子
type Node struct {
Next *Node
Value interface{}
}
var first *Node
func main(){
visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
if visited[n] {
fmt.Println("cycle detected")
break
}
visited[n] = true
fmt.Println(n.Value)
}
}
這是一個(gè)檢測單向鏈表是否有環(huán)的比較笨的辦法,原理就是利用map判斷這個(gè)key為*Node的值在map中是否出現(xiàn)過來確定是否有環(huán)。
這里的visited就是map,從這里我們可以看到,指針類型也是comparable的,所以可以作為keytype,其次,調(diào)用if語句中的visited[n]的時(shí)候,我們巧妙的利用了bool類型的0值就是false這個(gè)原理,來判斷這個(gè)keytype是否已經(jīng)出現(xiàn)。
還是原文中的例子:
type Person struct {
Name string
Likes []string
}
var people []*Person
likes := make(map[string][]*Person)
for _, p := range people {
for _, l := range p.Likes {
likes[l] = append(likes[l], p)
}
}
for _, p := range likes["cheese"] {
fmt.Println(p.Name, "likes cheese.")
}
我們有一個(gè)自定義的struct,Person,里面存了人的名字和他/她的愛好,現(xiàn)在我們要寫一個(gè)簡單的小程序,把所有的people(人員)按照相同興趣進(jìn)行分類
我們這里的代碼就是利用兩個(gè)go里的特征,
1, range對(duì)于非nil的map,可以進(jìn)行遍歷,但是如果是nil的map(也就是沒有初始化的map),默認(rèn)按照空的map處理,也就是不運(yùn)行for循環(huán)的邏輯代碼
2, append支持非nil和nil 的map,都能進(jìn)行成功的append。這樣,就能簡化代碼
剛才提到map里的keytype必須是comparable的,go的文檔里有明確的定義:
The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types.
Notably absent from the list are slices, maps, and functions;
these types cannot be compared using ==, and may not be used as map keys.
線程安全(goroutine)
前面提到go的map不是線程安全的,因此需要加鎖,一般的方法是,定義一個(gè)embeded的struct,類似于子類
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
讀的時(shí)候,調(diào)用讀鎖
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
寫的時(shí)候,寫鎖
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
# 讀取順序
go的map是hashmap,所以讀取遍歷的順序是不保證的,如果業(yè)務(wù)需要保證key的遍歷順序,建議將key單獨(dú)保存到一個(gè)slice里
import "sort"
var m map[int]string
var keys []int
for k := range m {
keys = append(keys, k)
}
sort.Ints(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", m[k])
}