《Go 程序设计语言》4.3 Map 核心概念总结

1. Map 的本质

Map 是哈希表的引用,类型写作 map[K]V,存储键值对。

组成部分 要求/说明
Key 必须支持 == 比较(所以 slicemapfunction 不能做 key)
Value 无任何类型限制

2. 创建方式

1
2
3
4
5
6
7
8
9
10
11
//make 创建(推荐用于需要指定初始容量的场景)
ages := make(map[string]int)

//字面量初始化
ages := map[string]int{
"alice": 31,
"charlie": 34,
}

//空 map(等价于 make,但更简洁)
ages := map[string]int{}

3. 核心操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//赋值
ages["alice"] = 32

//读取(key 不存在返回 value 类型的零值)
age := ages["bob"] // 0

//判断 key 是否存在(逗号 ok 惯用法)
age, ok := ages["bob"]
if !ok {
/* bob 不存在 */
}

//常见简写:读取并判断
if age, ok := ages["bob"]; ok {
// bob 存在,使用 age
}

//删除(key 不存在也不会 panic,安全操作)
delete(ages, "alice")

//遍历(⚠️ 顺序随机,每次运行不同)
for name, age := range ages {
fmt.Printf("%s\t%d\n", name, age)
}

//长度
len(ages)

4. nil map vs 空 map

1
2
3
4
5
var m map[string]int    // nil map(未初始化)
// 方式一:make(已初始化,非 nil)
m = make(map[string]int)
// 方式二:字面量(已初始化,非 nil)
m = map[string]int{}
操作 nil map 空 map
读取 ✅ 安全,返回零值 ✅ 安全
len() ✅ 返回 0 ✅ 返回 0
delete() ✅ 安全 ✅ 安全
写入 panic ✅ 正常

重要:向 nil map 写入会 panic,使用前必须先 make 或用字面量初始化。


5. 重要限制

❌ 不能对 map 元素取地址

1
_ = &ages["alice"]  // ❌ 编译错误

💡 原因:map 增长时可能重新分配内存,元素地址会失效。

❌ 不能用 == 比较两个 map

1
2
3
a := map[string]int{"x": 1}
b := map[string]int{"x": 1}
// a == b ❌ 编译错误(只能与 nil 比较)

需要手动实现比较逻辑:

1
2
3
4
5
6
7
8
9
func equal(x, y map[string]int) bool {
if len(x) != len(y) { return false }
for k, xv := range x {
if yv, ok := y[k]; !ok || yv != xv {
return false
}
}
return true
}

6. 有序遍历

map 遍历顺序是随机的,需要排序时:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 提取所有 key
names := make([]string, 0, len(ages))
for name := range ages {
names = append(names, name)
}

// 2. 对 key 排序
sort.Strings(names)

// 3. 按排序后的 key 遍历
for _, name := range names {
fmt.Printf("%s\t%d\n", name, ages[name])
}

7. 实用技巧

🔹 用 map 实现 Set

Go 没有内置 Set 类型,用 map[string]boolmap[string]struct{} 代替:

1
2
3
4
5
seen := make(map[string]bool)
if !seen[item] {
seen[item] = true
// 处理 item...
}

💡 更省内存的写法:map[string]struct{}{}struct{} 零大小)

🔹 不可比较类型做 Key

slice 不能直接做 key,先转成可比较类型(如 string):

1
2
3
4
5
6
func k(list []string) string { 
return fmt.Sprintf("%q", list)
}

m := make(map[string]int)
m[k(mySlice)]++

🔹 嵌套 Map(惰性初始化)

1
2
3
4
5
6
7
8
9
graph := make(map[string]map[string]bool)

func addEdge(from, to string) {
// ⚠️ 内层 map 需要单独初始化
if graph[from] == nil {
graph[from] = make(map[string]bool)
}
graph[from][to] = true
}

🔑 关键要点速记

要点 说明
Key 的要求 必须支持 ==slice/map/function 不能做 key
nil map 写入 panic,必须先 make 或字面量初始化
不存在的 key 读取返回零值,不会报错
判断存在 v, ok := m[key],用 ok 判断
遍历顺序 随机,需要有序遍历要自己排序
不能取地址 &m[key] 编译错误
不能互相比较 只能与 nil 比较,不能 a == b
嵌套 map 内层 map 需要单独初始化,否则写入 panic

📌 一句话总结:Map 是 Go 中强大的键值对容器,理解其引用语义、初始化要求和随机遍历特性,是避免 panic 和写出健壮代码的关键。