《快学 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 语言》更多章节

相关文章
相关标签/搜索