對(duì)于C程序員出身的Gopher來說,map類型是和切片、interface一樣能讓他們感受到Go語言先進(jìn)性的重要語法元素。map類型也是Go語言中最常用的數(shù)據(jù)類型之一。
go 中 map 怎么表現(xiàn)?
一些有關(guān)Go語言的中文教程或譯本將map稱為字典或哈希表,但在這里我選擇不譯,直接使用map。map是Go語言提供的一種抽象數(shù)據(jù)類型,它表示一組無序的鍵值對(duì)(key-value,后續(xù)我們會(huì)直接使用key和value分別表示鍵和值)。
map類型不支持“零值可用”,未顯式賦初值的map類型變量的零值為nil。對(duì)處于零值狀態(tài)的map變量進(jìn)行操作將會(huì)導(dǎo)致運(yùn)行時(shí)panic:
var m map[string]int // m = nil
m["key"] = 1 // panic: assignment to entry in nil map
簡(jiǎn)單來說就是不能不賦值,只對(duì)key 賦值是不行的。
我們必須對(duì)map類型變量進(jìn)行顯式初始化后才能使用它。
和切片一樣,創(chuàng)建map類型變量有兩種方式:
- 一種是使用復(fù)合字面值,
1)使用復(fù)合字面值創(chuàng)建map類型變量
// $GOROOT/src/net/status.go
var statusText = map[int]string{
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
...
}
- 一種是使用make這個(gè)預(yù)聲明的內(nèi)置函數(shù)。
2)使用make創(chuàng)建map類型變量
// $GOROOT/src/net/client.go
icookies = make(map[string][]*Cookie)
// $GOROOT/src/net/h2_bundle.go
http2commonLowerHeader = make(map[string]string, len(common))
和切片一樣,map也是引用類型,將map類型變量作為函數(shù)參數(shù)傳入不會(huì)有很大的性能損耗,并且在函數(shù)內(nèi)部對(duì)map變量的修改在函數(shù)外部也是可見的,比如下面的例子:
func foo(m map[string]int) {
m["key1"] = 11
m["key2"] = 12
}
func main() {
m := map[string]int{
"key1": 1,
"key2": 2,
}
fmt.Println(m) // map[key1:1 key2:2]
foo(m)
fmt.Println(m) // map[key1:11 key2:12]
}
map的基本操作
1. 插入數(shù)據(jù)
面對(duì)一個(gè)非nil的map類型變量,我們可以向其中插入符合map類型定義的任意鍵值對(duì)。
Go運(yùn)行時(shí)會(huì)負(fù)責(zé)map內(nèi)部的內(nèi)存管理,因此除非是系統(tǒng)內(nèi)存耗盡,我們不用擔(dān)心向map中插入數(shù)據(jù)的數(shù)量。
m := make(map[K]V)
m[k1] = v1
m[k2] = v2
m[k3] = v3
如果key已經(jīng)存在于map中,則該插入操作會(huì)用新值覆蓋舊值:
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
m["key1"] = 11 // 11會(huì)覆蓋掉舊值1
m["key3"] = 3 // map[key1:11 key2:2 key3:3]
2. 獲取數(shù)據(jù)個(gè)數(shù)
和切片一樣,map也可以通過內(nèi)置函數(shù)len獲取當(dāng)前已經(jīng)存儲(chǔ)的數(shù)據(jù)個(gè)數(shù):
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
fmt.Println(len(m)) // 2
m["key3"] = 3
fmt.Println(len(m)) // 3
3. 查找和數(shù)據(jù)讀取
map類型更多用在查找和數(shù)據(jù)讀取場(chǎng)合。所謂查找就是判斷某個(gè)key是否存在于某個(gè)map
中。我們可以使用“comma ok”慣用法來進(jìn)行查找
:
_, ok := m["key"]
if !ok {
// "key"不在map中
}
這里我們并不關(guān)心某個(gè)key對(duì)應(yīng)的value,而僅僅關(guān)心某個(gè)key是否在map中,因此我們
使用空標(biāo)識(shí)符(blank identifier)忽略了可能返回的數(shù)據(jù)值,而僅關(guān)心ok的值是否為
true(表示在map中)。
如果要讀取key對(duì)應(yīng)的value的值,我們可能會(huì)寫出下面這樣的代碼:
m := map[string]int
m["key1"] = 1
m["key2"] = 2
v := m["key1"]
fmt.Println(v) // 1
v = m["key3"]
fmt.Println(v) // 0
上面的代碼在key存在于map中(如“key1”)的情況下是沒有問題的。但是如果key不
存在于map中(如“key3”),我們看到v仍然被賦予了一個(gè)“合法”值0,這個(gè)值是value
類型int的零值。在這樣的情況下,我們無法判定這個(gè)0是“key3”對(duì)應(yīng)的值還是
因“key3”不存在而返回的零值。為此我們還需要借助“comma ok”慣用法
m := map[string]int
v, ok := m["key"]
if !ok {
// "key"不在map中
}
fmt.Println(v)
我們需要通過ok的值來判定key是否存在于map中。只有當(dāng)ok = true時(shí),所獲得的
value值才是我們所需要的。綜上,Go語言的一個(gè)最佳實(shí)踐是總是使用“comma ok”慣用法讀取map中的值。
4. 刪除數(shù)據(jù)
我們借助內(nèi)置函數(shù)delete從map中刪除數(shù)據(jù):
m := map[string]int {
"key1" : 1,
"key2" : 2,
}
fmt.Println(m) // map[key1:1 key2:2]
delete(m, "key2")
fmt.Println(m) // map[key1:1]
注意,即便要?jiǎng)h除的數(shù)據(jù)在map中不存在,delete也不會(huì)導(dǎo)致panic。
5. 遍歷數(shù)據(jù)
我們可以像對(duì)待切片那樣通過for range語句對(duì)map中的數(shù)據(jù)進(jìn)行遍歷:
func main() {
m := map[int]int{
1: 11,
2: 12,
3: 13,
}
fmt.Printf("{ ")
for k, v := range m {
fmt.Printf("[%d, %d] ", k, v)
}
fmt.Printf("}\n")
}
我們看到對(duì)同一map做多次遍歷,遍歷的元素次序并不相同。這是因?yàn)镚o運(yùn)行時(shí)在初始
化map迭代器時(shí)對(duì)起始位置做了隨機(jī)處理。因此千萬不要依賴遍歷map所得到的元素次序。文章來源:http://www.zghlxwxcb.cn/news/detail-704954.html
如果你需要一個(gè)穩(wěn)定的遍歷次序,那么一個(gè)比較通用的做法是使用另一種數(shù)據(jù)結(jié)構(gòu)來
按需要的次序保存key,比如切片:文章來源地址http://www.zghlxwxcb.cn/news/detail-704954.html
import "fmt"
func doIteration(sl []int, m map[int]int) {
fmt.Printf("{ ")
for _, k := range sl { // 按切片中的元素次序迭代
v, ok := m[k]
if !ok {
continue
}
fmt.Printf("[%d, %d] ", k, v)
}
fmt.Printf("}\n")
}
func main() {
var sl []int
m := map[int]int{
1: 11,
2: 12,
3: 13,
}
for k, _ := range m {
sl = append(sl, k) // 將元素按初始次序保存在切片中
}
for i := 0; i < 3; i++ {
doIteration(sl, m)
}
}
$go run map_stable_iterate.go
{ [1, 11] [2, 12] [3, 13] }
{ [1, 11] [2, 12] [3, 13] }
{ [1, 11] [2, 12] [3, 13] }
到了這里,關(guān)于go基礎(chǔ)07-了解map實(shí)現(xiàn)原理并高效使用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!