《快學 Go 語言》第 6 課 —— 字典

字典在數學上的詞彙是映射,將一個集合中的全部元素關聯到另外一個集合中的部分或所有元素,而且只能是一一映射或者多對一映射。數組

數組切片讓咱們具有了能夠操做一塊連續內存的能力,它是對同質元素的統一管理。而字典則賦予了不連續不一樣類的內存變量的關聯性,它表達的是一種因果關係,字典的 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 不存在會怎樣?

刪除操做時,若是對應的 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 語言》更多章節

相關文章
相關標籤/搜索