字典在數學上的詞彙是映射,將一個集合中的全部元素關聯到另外一個集合中的部分或所有元素,而且只能是一一映射或者多對一映射。數組
數組切片讓咱們具有了能夠操做一塊連續內存的能力,它是對同質元素的統一管理。而字典則賦予了不連續不一樣類的內存變量的關聯性,它表達的是一種因果關係,字典的 key 是因,字典的 value 是果。若是說數組和切片賦予了咱們步行的能力,那麼字典則讓咱們具有了跳躍的能力。安全
指針、數組切片和字典都是容器型變量,字典比數組切片在使用上要簡單不少,可是內部結構卻無比複雜。本節咱們只專一字典的基礎使用,在後續的高級章節再來分析它的內部結構。bash
關於 Go 語言有不少批評的聲音,好比說它不支持範型。其實嚴格來講 Go 是支持範型的,只不過很弱,範型在 Go 語言裏是一種很弱的存在。好比數組切片和字典類型都是支持範型的。在建立字典時,必需要給 key 和 value 指定類型。建立字典也可使用 make 函數app
package main
import "fmt"
func main() {
var m map[int]string = make(map[int]string)
fmt.Println(m, len(m))
}
----------
map[] 0
複製代碼
使用 make 函數建立的字典是空的,長度爲零,內部沒有任何元素。若是須要給字典提供初始化的元素,就須要使用另外一種建立字典的方式。函數
package main
import "fmt"
func main() {
var m map[int]string = map[int]string{
90: "優秀",
80: "良好",
60: "及格", // 注意這裏逗號不可缺乏,不然會報語法錯誤
}
fmt.Println(m, len(m))
}
---------------
map[90:優秀 80:良好 60:及格] 3
複製代碼
字典變量一樣支持類型推導,上面的變量定義能夠簡寫成ui
var m = map[int]string{
90: "優秀",
80: "良好",
60: "及格",
}
複製代碼
若是你能夠預知字典內部鍵值對的數量,那麼還能夠給 make 函數傳遞一個整數值,通知運行時提早分配好相應的內存。這樣能夠避免字典在長大的過程當中要經歷的屢次擴容操做。spa
var m = make(map[int]string, 16)
複製代碼
同 Python 語言同樣,字典可使用中括號來讀寫內部元素,使用 delete 函數來刪除元素。線程
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 不存在,delete 函數會靜默處理。遺憾的是 delete 函數沒有返回值,你沒法直接獲得 delete 操做是否真的刪除了某個元素。你須要經過長度信息或者提早嘗試讀取 key 對應的 value 來得知。指針
讀操做時,若是 key 不存在,也不會拋出異常。它會返回 value 類型對應的零值。若是是字符串,對應的零值是空串,若是是整數,對應的零值是 0,若是是布爾型,對應的零值是 false。code
你不能經過返回的結果是不是零值來判斷對應的 key 是否存在,由於 key 對應的 value 值可能剛好就是零值,好比下面的字典你就不能判斷 "durin" 是否存在
var m = map[string]int {
"durin": 0 // 舉個栗子而已,其實我仍是喜歡吃榴蓮的
}
複製代碼
這時候必須使用字典的特殊語法,以下
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
複製代碼
字典的下標讀取能夠返回兩個值,使用第二個返回值都表示對應的 key 是否存在。初學者看到這種奇怪的用法是須要花時間來消化的,讀者不須要想太多,它只是 Go 語言提供的語法糖,內部並無太多的玄妙。正常的函數調用能夠返回多個值,可是並不具有這種「隨機應變」的特殊能力 —— 「多態返回值」。
字典的遍歷提供了下面兩種方式,一種是須要攜帶 value,另外一種是隻須要 key,須要使用到 Go 語言的 range 關鍵字。
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 語言的字典沒有提供諸於 keys() 和 values() 這樣的方法,意味着若是你要獲取 key 列表,就得本身循環一下,以下
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]
複製代碼
這會讓代碼寫起來比較繁瑣,不過 Go 語言官方就是沒有提供,讀者仍是努力習慣一下吧
Go 語言的內置字典不是線程安全的,若是須要線程安全,必須使用鎖來控制。在後續鎖的章節裏,咱們將會本身實現一個線程安全的字典。
字典變量裏存的只是一個地址指針,這個指針指向字典的頭部對象。因此字典變量佔用的空間是一個字,也就是一個指針的大小,64 位機器是 8 字節,32 位機器是 4 字節。
可使用 unsafe 包提供的 Sizeof 函數來計算一個變量的大小
package main
import (
"fmt"
"unsafe"
)
func main() {
var m = map[string]int{
"apple": 2,
"pear": 3,
"banana": 5,
}
fmt.Println(unsafe.Sizeof(m))
}
------
8
複製代碼
在遍歷字典獲得 keys 和 values 的例子裏,咱們分配了 names 和 scores 兩個切片,若是把代碼片段調整成下面這樣,會有什麼問題?
var names = make([]string, len(fruits))
var scores = make([]int, len(fruits))
複製代碼
掃一掃二維碼閱讀《快學 Go 語言》更多章節