字典在数学上的词汇是映射,将一个集合中的全部元素关联到另外一个集合中的部分或所有元素,而且只能是一一映射或者多对一映射。数组
数组切片让咱们具有了能够操做一块连续内存的能力,它是对同质元素的统一管理。而字典则赋予了不连续不一样类的内存变量的关联性,它表达的是一种因果关系,字典的 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 语言》更多章节