先看它解决什么问题
班级只有 3 名学生时,把姓名和成绩放进列表,再从头比较姓名也能找到 Tom。可是一旦有 10 万名学生,每次查询都从第一项开始找,最坏要比较 10 万次。我们需要一种结构,把“按姓名寻找”变成“根据姓名直接缩小到很小的候选范围”。
只有列表
查询 Tom 时依次比较 Ada、Bob、……、Tom。数据越多,可能比较得越久。
使用 map
把姓名作为 key、成绩作为 value。查询时先根据姓名计算位置,再检查少量候选项。
map 的正式名称是 key-value(键值对)数据结构:key(键)像姓名或编号,用来定位数据;value(值)是这个键对应的实际内容。下面的 scores 代表“全班成绩表”,student 代表“这次要查询的学生”。
package main import "fmt" func main() { scores := make(map[string]int) scores["Tom"] = 90 student := "Tom" score := scores[student] fmt.Println(score) }
map[string]int 表示 key 的类型是 string(字符串),value 的类型是 int(整数)。类型写在这里,是为了限制这张 map 允许存什么数据。
Tom,得到成绩 90。业务代码只写了一次按 key 读取;后面的章节负责解释运行时怎样避免从头扫描所有学生。| 语法 | 含义 |
|---|---|
make(map[string]int) | make 负责初始化,创建一张可写的 map |
m["Tom"] = 90 | 写入 / 更新 |
v := m["Tom"] | 读取;不存在时返回 value 类型的零值,也就是该类型的默认值,例如 int 的 0 |
v, ok := m["Tom"] | 读取并判断 key 是否存在 |
delete(m, "Tom") | 删除;key 不存在也安全 |
v, ok := m[key]。如果 value 是 int,m["Tom"] == 0 既可能表示 key 不存在,也可能表示 Tom 的值本来就是 0;此时 ok 才能把两种情况区分开。最小对照:0 分和没这个学生
scores := map[string]int{"Tom": 0} tomScore, tomExists := scores["Tom"] // 0, true:学生存在,成绩就是 0 bobScore, bobExists := scores["Bob"] // 0, false:学生不存在
两次读取都得到数字 0,只改变 key 是否存在,ok 的结果就不同。这就是 comma-ok 真正解决的歧义。
nil map
nil 表示这个 map 变量目前没有指向已经初始化的底层存储。它看起来像一个空 map,因此可以读,但还没有可写入的空间;强行写入会触发 panic,也就是程序发现无法继续执行当前操作时抛出的运行时错误。
var m map[string]int fmt.Println(m["a"]) // 0,可以读 m["a"] = 1 // panic: assignment to entry in nil map
key 为什么必须可比较
“可比较”是指两个值可以使用 == 判断是否相等。hash 只能帮你快速缩小范围,最终仍要判断“候选 key 和查询 key 是否真的相等”。所以 map key 必须可比较。slice(动态长度序列)、map 和 func(函数值)不能做 key;数组和结构体只有内部字段都可比较时才可以。