字典在數(shù)學(xué)上的詞匯是映射,將一個(gè)集合中的所有元素關(guān)聯(lián)到另一個(gè)集合中的部分或全部元素,并且只能是一一映射或者多對(duì)一映射腕让。
數(shù)組切片讓我們具備了可以操作一塊連續(xù)內(nèi)存的能力,它是對(duì)同質(zhì)元素的統(tǒng)一管理歧斟。而字典則賦予了不連續(xù)不同類的內(nèi)存變量的關(guān)聯(lián)性纯丸,它表達(dá)的是一種因果關(guān)系,字典的 key 是因静袖,字典的 value 是果觉鼻。如果說(shuō)數(shù)組和切片賦予了我們步行的能力,那么字典則讓我們具備了跳躍的能力队橙。
指針坠陈、數(shù)組切片和字典都是容器型變量,字典比數(shù)組切片在使用上要簡(jiǎn)單很多喘帚,但是內(nèi)部結(jié)構(gòu)卻無(wú)比復(fù)雜畅姊。本節(jié)我們只專注字典的基礎(chǔ)使用,在后續(xù)的高級(jí)章節(jié)再來(lái)分析它的內(nèi)部結(jié)構(gòu)吹由。
字典的創(chuàng)建
關(guān)于 Go 語(yǔ)言有很多批評(píng)的聲音若未,比如說(shuō)它不支持范型。其實(shí)嚴(yán)格來(lái)說(shuō) Go 是支持范型的倾鲫,只不過(guò)很弱粗合,范型在 Go 語(yǔ)言里是一種很弱的存在。比如數(shù)組切片和字典類型都是支持范型的乌昔。在創(chuàng)建字典時(shí)隙疚,必須要給 key 和 value 指定類型。創(chuàng)建字典也可以使用 make 函數(shù)
package main
import "fmt"
func main() {
var m map[int]string = make(map[int]string)
fmt.Println(m, len(m))
}
----------
map[] 0
使用 make 函數(shù)創(chuàng)建的字典是空的磕道,長(zhǎng)度為零供屉,內(nèi)部沒(méi)有任何元素。如果需要給字典提供初始化的元素溺蕉,就需要使用另一種創(chuàng)建字典的方式伶丐。
package main
import "fmt"
func main() {
var m map[int]string = map[int]string{
90: "優(yōu)秀",
80: "良好",
60: "及格", // 注意這里逗號(hào)不可缺少,否則會(huì)報(bào)語(yǔ)法錯(cuò)誤
}
fmt.Println(m, len(m))
}
---------------
map[90:優(yōu)秀 80:良好 60:及格] 3
字典變量同樣支持類型推導(dǎo)疯特,上面的變量定義可以簡(jiǎn)寫成
var m = map[int]string{
90: "優(yōu)秀",
80: "良好",
60: "及格",
}
如果你可以預(yù)知字典內(nèi)部鍵值對(duì)的數(shù)量哗魂,那么還可以給 make 函數(shù)傳遞一個(gè)整數(shù)值,通知運(yùn)行時(shí)提前分配好相應(yīng)的內(nèi)存漓雅。這樣可以避免字典在長(zhǎng)大的過(guò)程中要經(jīng)歷的多次擴(kuò)容操作录别。
var m = make(map[int]string, 16)
字典的讀寫
同 Python 語(yǔ)言一樣朽色,字典可以使用中括號(hào)來(lái)讀寫內(nèi)部元素,使用 delete 函數(shù)來(lái)刪除元素组题。
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
// 讀取元素
var score = fruits["banana"]
fmt.Println(score)
// 增加或修改元素
fruits["pear"] = 3
fmt.Println(fruits)
// 刪除元素
delete(fruits, "pear")
fmt.Println(fruits)
}
-----------------------
5
map[apple:2 banana:5 orange:8 pear:3]
map[orange:8 apple:2 banana:5]
字典 key 不存在會(huì)怎樣葫男?
刪除操作時(shí),如果對(duì)應(yīng)的 key 不存在往踢,delete 函數(shù)會(huì)靜默處理腾誉。遺憾的是 delete 函數(shù)沒(méi)有返回值,你無(wú)法直接得到 delete 操作是否真的刪除了某個(gè)元素峻呕。你需要通過(guò)長(zhǎng)度信息或者提前嘗試讀取 key 對(duì)應(yīng)的 value 來(lái)得知利职。
讀操作時(shí),如果 key 不存在瘦癌,也不會(huì)拋出異常猪贪。它會(huì)返回 value 類型對(duì)應(yīng)的零值。如果是字符串讯私,對(duì)應(yīng)的零值是空串热押,如果是整數(shù),對(duì)應(yīng)的零值是 0斤寇,如果是布爾型桶癣,對(duì)應(yīng)的零值是 false。
你不能通過(guò)返回的結(jié)果是否是零值來(lái)判斷對(duì)應(yīng)的 key 是否存在娘锁,因?yàn)?key 對(duì)應(yīng)的 value 值可能恰好就是零值牙寞,比如下面的字典你就不能判斷 "durin" 是否存在
var m = map[string]int {
"durin": 0 // 舉個(gè)栗子而已,其實(shí)我還是喜歡吃榴蓮的
}
這時(shí)候必須使用字典的特殊語(yǔ)法莫秆,如下
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
var score, ok = fruits["durin"]
if ok {
fmt.Println(score)
} else {
fmt.Println("durin not exists")
}
fruits["durin"] = 0
score, ok = fruits["durin"]
if ok {
fmt.Println(score)
} else {
fmt.Println("durin still not exists")
}
}
-------------
durin not exists
0
字典的下標(biāo)讀取可以返回兩個(gè)值间雀,使用第二個(gè)返回值都表示對(duì)應(yīng)的 key 是否存在河胎。初學(xué)者看到這種奇怪的用法是需要花時(shí)間來(lái)消化的辕羽,讀者不需要想太多,它只是 Go 語(yǔ)言提供的語(yǔ)法糖气筋,內(nèi)部并沒(méi)有太多的玄妙缝驳。正常的函數(shù)調(diào)用可以返回多個(gè)值连锯,但是并不具備這種“隨機(jī)應(yīng)變”的特殊能力 —— 「多態(tài)返回值」。
字典的遍歷
字典的遍歷提供了下面兩種方式用狱,一種是需要攜帶 value萎庭,另一種是只需要 key,需要使用到 Go 語(yǔ)言的 range 關(guān)鍵字齿拂。
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
for name, score := range fruits {
fmt.Println(name, score)
}
for name := range fruits {
fmt.Println(name)
}
}
------------
orange 8
apple 2
banana 5
apple
banana
orange
奇怪的是,Go 語(yǔ)言的字典沒(méi)有提供諸于 keys() 和 values() 這樣的方法肴敛,意味著如果你要獲取 key 列表署海,就得自己循環(huán)一下吗购,如下
package main
import "fmt"
func main() {
var fruits = map[string]int {
"apple": 2,
"banana": 5,
"orange": 8,
}
var names = make([]string, 0, len(fruits))
var scores = make([]int, 0, len(fruits))
for name, score := range fruits {
names = append(names, name)
scores = append(scores, score)
}
fmt.Println(names, scores)
}
----------
[apple banana orange] [2 5 8]
這會(huì)讓代碼寫起來(lái)比較繁瑣,不過(guò) Go 語(yǔ)言官方就是沒(méi)有提供砸狞,讀者還是努力習(xí)慣一下吧
線程(協(xié)程)安全
Go 語(yǔ)言的內(nèi)置字典不是線程安全的捻勉,如果需要線程安全,必須使用鎖來(lái)控制刀森。在后續(xù)鎖的章節(jié)里踱启,我們將會(huì)自己實(shí)現(xiàn)一個(gè)線程安全的字典。
字典變量里存的是什么研底?
字典變量里存的只是一個(gè)地址指針埠偿,這個(gè)指針指向字典的頭部對(duì)象。所以字典變量占用的空間是一個(gè)字榜晦,也就是一個(gè)指針的大小冠蒋,64 位機(jī)器是 8 字節(jié),32 位機(jī)器是 4 字節(jié)乾胶。
可以使用 unsafe 包提供的 Sizeof 函數(shù)來(lái)計(jì)算一個(gè)變量的大小
package main
import (
"fmt"
"unsafe"
)
func main() {
var m = map[string]int{
"apple": 2,
"pear": 3,
"banana": 5,
}
fmt.Println(unsafe.Sizeof(m))
}
------
8