map:键值对的字典,以及三个最容易咬人的细节¶
这一章讲 Go 的字典:map。 它按「键」存「值」——像一本电话簿, 报人名、查号码,不用从头翻到尾。读完你会走通 map 的一生: 出生(make 或字面量)、查值(以及「查不到」的陷阱)、删键、遍历, 以及它和切片一样的引用本质。
1. map 是什么¶
书里给的定义:map 是以「键值对」存值的数据结构;键唯一、不能改, 键的类型可以是任何「可比较」的类型(可比较 = 能用 == 判等的类型: 数字、字符串、布尔、指针、结构体(字段都可比较时)等; 切片和 map 自己不能当键,因为它们不可比较)1。
别名顺带记一下:别的语言里它叫哈希(hash)或字典(dictionary)1。 用它图什么,书里列了四条:快查快取、按键直达、按键删除、实现哈希表2。
2. 主线例子:一部电话簿的完整一生¶
照原书的 phone_dict 走完全程(键是 string 人名,值是 int64 电话号)3。
出生——三种姿势。
var phone_dict map[string]int64 // 声明:这是 nil map!
phone_dict2 := map[string]int64{ // 字面量:带着数据出生
"Rahul": 9876543201,
"Swati": 9123456789,
}
phone_dict3 := make(map[string]int64) // make:空但可用
细节一(本章第一个坑):nil map 只许读、不许写。 书里说得很直白:从 nil map 读,返回空结果;往 nil map 写,运行时直接报错4。 所以声明了 map 变量,要么用字面量、要么用 make 初始化,裸着用会崩—— 和切片不一样(nil 切片能 append,nil map 不能写)。
日常——增、查、改。
phone_dict2["aayush"] = 9823165748 // 增/改:键不存在就建,存在就覆盖
fmt.Println(phone_dict2["Swati"]) // → 9123456789
细节二(本章最重要的坑):查不存在的键,不报错,返回值的零值。
对照数组:数组越界是错误,map 查无此键不是错误—— 它安安静静返回 int64 的零值 05。这意味着什么? 如果你的电话簿里真存了一个号码 0,或者你拿 0 当「没有」的信号, 程序会悄悄出错。解药是下面的「逗号 ok 模式」。
3. 查键:逗号 ok 模式与取出所有键¶
val, ok := m[key] 是检查键是否存在的标准姿势:
键在,ok = true,val 是对应的值;键不在,ok = false,val 是该类型的零值6:
书里特意指出这和接口的类型断言是同一个模式(t, ok := i.(T))6——
Go 里「一个动作可能失败」的地方,都给你一对「结果 + 成没成」。
想要所有键?书里用了反射包的 MapKeys()(有点重);
日常更常用的是遍历(下节)时收集7。
4. 遍历:没有顺序,要有序先排序键¶
for key := range phone_dict3 { // 只拿键
fmt.Println(key)
}
for key, val := range phone_dict3 { // 键和值都拿
fmt.Println(key, val)
}
细节三(本章第三个坑):map 的遍历顺序是随机的。 书里明说:map 不保证任何顺序,每次运行的输出顺序都可能不同; 要按顺序处理,先把键收集进切片、排序,再按排好的键取值8:
var keys []string
for k := range phone_dict3 {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println("Key:", k, "Value:", phone_dict3[k])
}
// → Key: Deepak Value: 8000000000 / Key: Mayank …(按字母序)
这个「随机」是 Go 有意的设计:防止程序员无意中依赖某个顺序, 把偶然当必然(补充,不在书里,来自通用知识——顺序随机化的机制确为语言有意为之)。
5. 删除与比较¶
delete(m, key) 删键:不返回任何东西;键不存在也不报错,安安静静什么都不做9:
比较:两个层面,规矩不同。
书里演示了 DeepEqual 的灵敏:改一个值(Swati 换成别的号),
两个原本相等的 map 立刻不等10。
map 不能直接用 == 互相比较(编译错),这点和切片一致。
6. 引用类型:赋值不等于复制¶
map 和切片一样是指向内部结构的引用。把一个 map 赋给另一个变量, 两边指向同一份数据;一边改,另一边看得见。传进函数也一样11:
phone_dict["Mayank"] → 9342516789
phone_dict1 := phone_dict // 复制的是「指针」,不是数据
phone_dict1["Mayank"] = 1234567890
phone_dict["Mayank"] → 1234567890 ← 原 map 也被改了!
传参同理:change_value(phone_dict)
函数里 phone_dict1["Mayank"] = 1234567890
出来后 phone_dict["Mayank"] 跟着变
图说:赋值/传参只复制了指向数据的门牌号,数据本身始终只有一份11。 (想要一份真正的副本?得手动建新 map 逐个搬——书里没讲这个惯用写法, 用到时记住方向即可。)
判断(我们的,不是书里的): map 的「引用语义 + 随机遍历 + 零值陷阱」 三件套,把它钉死在「好用但必须有纪律」的位置上: 查键必用逗号 ok;跨函数共享 map 前要意识到对方会改; 需要有序就用键切片排序。少了任何一条,bug 都是潜伏型而非当场爆。 如果错,会错在: Go 标准库(如 sync.Map、一些并发容器)在某些场景 提供了更合适的替代——但那是并发专题的事,单线程下原生 map 仍是默认答案。
7. 作者的判断与证据¶
这一章是规则与机制,没有推测。值得表扬的一处: 「nil map 读可以写报错」「查无键返回零值」这两个坑,书里都主动点破了, 没有让读者自己踩(不少入门书会漏)45。
8. 边界与局限¶
m[k]的零值陷阱的完整解法就是逗号 ok;书里没有再讲 「值本身可能是零值」的二级场景(比如 map[string]int 里存了合法的 0);- 并发读写 map 会崩(fatal error: concurrent map read and map write) ——这是 Go 运行时的一条硬规则,本书直到第 16 章才间接涉及;
- 书中取所有键用
reflect.ValueOf(m).MapKeys(),属于「杀鸡用牛刀」, 日常用 range 收集即可; - 复合键(map 的键是结构体)、map 嵌套的惯用写法未展开。
9. 可带走的¶
- map = 键值对字典;键唯一、键类型必须可比较(切片/map 不能当键);
- 出生三姿势:声明(nil)、字面量、make;nil map 只许读不许写;
- 查无此键返回零值、不报错——判断存在必须用
val, ok := m[key]; len(m)是键的数量;- 遍历顺序随机;要顺序,先收集键、sort 排序、再取值;
delete(m, k)删键,键不存在也不报错;- map 之间不能用
==,判全等用reflect.DeepEqual,判空用== nil; - map 是引用类型:赋值/传参共享同一份数据,函数里改外面看得见;
- 逗号 ok 模式是 Go 的通用失败信号(map、类型断言同款);
- 想让函数拥有自己的副本,手动建新 map 逐个拷贝。
10. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| map 定义与用途 | Maps | text/22-fm-learning-go-programming.txt:49(搜「key-value pair」) · text/22-fm-learning-go-programming.txt:58(搜「Fast lookups」) |
| 声明与 nil 默认值 | Maps | text/22-fm-learning-go-programming.txt:76(搜「map [type_of_keys]」) · text/22-fm-learning-go-programming.txt:91(搜「nil, as it does not have any key」) |
| nil map 读可以写报错 | Maps | text/22-fm-learning-go-programming.txt:97(搜「attempting to write to a nil map」) |
| 字面量与 make 初始化 | Maps | text/22-fm-learning-go-programming.txt:100(搜「map [string] int64{}」) · text/22-fm-learning-go-programming.txt:127(搜「make function is also used」) |
| 查无键返回零值 | Maps | text/22-fm-learning-go-programming.txt:145(搜「does not belong to that map」) · text/22-fm-learning-go-programming.txt:217(搜「0」) |
| len 与逗号 ok 模式 | Maps | text/22-fm-learning-go-programming.txt:253(搜「length of phone_dict3」) · text/22-fm-learning-go-programming.txt:280(搜「type assertion in the interface」) · text/22-fm-learning-go-programming.txt:343(搜「false 0」) |
| MapKeys 取所有键 | Maps | text/22-fm-learning-go-programming.txt:313(搜「MapKeys」) · text/22-fm-learning-go-programming.txt:337(搜「Keys of phone_dict3」) |
| 遍历无序与排序键 | Maps | text/22-fm-learning-go-programming.txt:349(搜「no order is specified」) · text/22-fm-learning-go-programming.txt:406(搜「sort.Strings」) |
| delete | Maps | text/22-fm-learning-go-programming.txt:445(搜「does not return anything」) · text/22-fm-learning-go-programming.txt:499(搜「After deleting a key」) |
| DeepEqual 比较 | Maps | text/22-fm-learning-go-programming.txt:514(搜「same keys and the same key's associated values」) · text/22-fm-learning-go-programming.txt:574(搜「false true false」) |
| 引用类型语义 | Maps | text/22-fm-learning-go-programming.txt:580(搜「refer to the same data structure」) · text/22-fm-learning-go-programming.txt:631(搜「1234567890」) · text/22-fm-learning-go-programming.txt:691(搜「After calling function」) |
-
出处:「Maps」第 49 段(
text/22-fm-learning-go-programming.txt:49,搜「key-value pair」)。「键可比较」的完整口径:切片、map、函数不可比较,不能当键(补充自 Go 语言规范)。 ↩↩ -
出处:「Maps」第 55–67 段(
text/22-fm-learning-go-programming.txt:58,搜「Fast lookups」)。 ↩ -
出处:「Maps」第 70–218 段(
text/22-fm-learning-go-programming.txt:100,搜「map [string] int64{}」;text/22-fm-learning-go-programming.txt:121,搜「aayush」)。 ↩ -
出处:「Maps」第 97 段(
text/22-fm-learning-go-programming.txt:97,搜「attempting to write to a nil map」)。 ↩↩ -
出处:「Maps」第 145–217 段(
text/22-fm-learning-go-programming.txt:145,搜「does not belong to that map」;text/22-fm-learning-go-programming.txt:217,搜「0」)。 ↩↩ -
出处:「Maps」第 274–343 段(
text/22-fm-learning-go-programming.txt:280,搜「type assertion in the interface」;text/22-fm-learning-go-programming.txt:340,搜「true 9123456789」;text/22-fm-learning-go-programming.txt:343,搜「false 0」)。 ↩↩ -
出处:「Maps」第 301–337 段(
text/22-fm-learning-go-programming.txt:313,搜「MapKeys」)。 ↩ -
出处:「Maps」第 346–439 段(
text/22-fm-learning-go-programming.txt:349,搜「no order is specified」;text/22-fm-learning-go-programming.txt:424,搜「Rahul Swati Suman Mayank Deepak」;text/22-fm-learning-go-programming.txt:427,搜「Key: Deepak」)。 ↩ -
出处:「Maps」第 442–502 段(
text/22-fm-learning-go-programming.txt:445,搜「does not return anything」;text/22-fm-learning-go-programming.txt:499,搜「After deleting a key」)。 ↩ -
出处:「Maps」第 505–574 段(
text/22-fm-learning-go-programming.txt:514,搜「same keys and the same key's associated values」;text/22-fm-learning-go-programming.txt:574,搜「false true false」)。 ↩ -
出处:「Maps」第 577–691 段(
text/22-fm-learning-go-programming.txt:580,搜「refer to the same data structure」;text/22-fm-learning-go-programming.txt:631,搜「1234567890」;text/22-fm-learning-go-programming.txt:691,搜「After calling function」)。 ↩↩