7.2 Go Map 进阶
1. Map 的值为切片 --- map[K][]V
map 的值可以是切片,实现"一个键对应多个值"的数据结构:
go
package main
import "fmt"
func main() {
// map[string][]int --- 学生名 → 成绩列表
studentScores := make(map[string][]int)
// 直接赋值切片
studentScores["Alice"] = []int{92, 88, 95}
studentScores["Bob"] = []int{78, 82, 85}
studentScores["Charlie"] = []int{90, 87, 93}
// 也可以 append 添加(nil 切片 append 安全)
// studentScores["David"] = append(studentScores["David"], 80)
fmt.Println("Student scores:", studentScores)
}
执行结果:
Student scores: map[Alice:[92 88 95] Bob:[78 82 85] Charlie:[90 87 93]]
要点:
map[string][]int--- 值类型是[]int切片,一个学生对应多个成绩- 赋值方式1:直接赋值切片
studentScores["Alice"] = []int{92, 88, 95} - 赋值方式2:逐个 append
studentScores["Alice"] = append(studentScores["Alice"], 92) - nil 切片 append 安全:
studentScores["David"]为 nil,append 自动分配底层数组 - 这是分组模式的基础:按键归类多个值
2. 嵌套 map --- 安全访问两层键
嵌套 map 访问时必须逐层检查键是否存在,否则可能 panic:
go
package main
import "fmt"
func main() {
// 嵌套 map:科目 → 学生 → 成绩
gradebook := make(map[string]map[string]int)
// 必须初始化内层 map!
gradebook["Math"] = make(map[string]int)
gradebook["Science"] = make(map[string]int)
gradebook["Math"]["Alice"] = 92
gradebook["Math"]["Bob"] = 85
gradebook["Science"]["Alice"] = 89
gradebook["Science"]["Bob"] = 88
// 安全访问:两层 comma-ok 检查
// 先检查 "Math" 是否存在,再检查 "Alice" 是否存在
mathGrades, mathExists := gradebook["Math"]
if mathExists {
aliceMath, aliceExists := mathGrades["Alice"]
if aliceExists {
fmt.Printf("Alice's Math grade: %d\n", aliceMath)
}
}
fmt.Println("Gradebook:", gradebook)
}
执行结果:
Alice's Math grade: 92
Gradebook: map[Math:map[Alice:92 Bob:85] Science:map[Alice:89 Bob:88]]
要点:
make(map[string]map[string]int)--- 外层科目,内层学生成绩- 必须初始化内层 map :
gradebook["Math"] = make(map[string]int)- 不初始化:
gradebook["Math"]["Alice"] = 92会 panic(内层是 nil map)
- 不初始化:
- 两层 comma-ok 安全访问:
- 第1层:
mathGrades, mathExists := gradebook["Math"] - 第2层:
aliceMath, aliceExists := mathGrades["Alice"]
- 第1层:
- 不安全访问的后果:
gradebook["English"]["Alice"]--- "English" 不存在,返回 nil 内层 map,写入 panicgradebook["Math"]["Unknown"]--- "Unknown" 不存在,返回零值 0(不 panic,但可能误判)
3. Map 的值为结构体 --- 修改需重新赋值
map 中结构体值是不可直接修改字段的,必须取出 → 修改 → 重新赋值:
go
package main
import "fmt"
func main() {
type Person struct {
Age int
City string
}
people := make(map[string]Person)
people["John"] = Person{Age: 30, City: "New York"}
people["Jane"] = Person{Age: 25, City: "Los Angeles"}
// 错误方式:直接修改字段(编译报错!)
// people["John"].Age = 31 // compile error: cannot assign to struct field in map
// 正确方式:取出 → 修改 → 重新赋值
john := people["John"] // 取出结构体(值拷贝)
john.Age = 31 // 修改拷贝
people["John"] = john // 重新赋值回 map
fmt.Println("People:", people)
}
执行结果:
People: map[Jane:{25 Los Angeles} John:{31 New York}]
要点:
-
people["John"].Age = 31--- 编译报错!Go 不允许直接修改 map 中结构体的字段 -
原因:map 的值是不可寻址的(addressable)。
people["John"]返回的是值的拷贝,不是引用 -
正确步骤:
john := people["John"]--- 取出值拷贝john.Age = 31--- 修改拷贝的字段people["John"] = john--- 把修改后的值重新赋值回 map
-
这个限制只针对值类型 (struct、int 等),如果值是指针类型 则可以直接修改:
gopeople := make(map[string]*Person) people["John"] = &Person{Age: 30, City: "New York"} people["John"].Age = 31 // 可以!指针可寻址 -
也可以用指针 map 避免反复取出赋值:
map[string]*Person
4. Map 模拟集合(Set)--- 去重
用 map[key]bool 的键来模拟集合,实现去重:
go
package main
import "fmt"
func main() {
uniqueWords := make(map[string]bool)
words := []string{"hello", "world", "hello", "go", "world"}
// 添加到集合:键存在即表示该词出现过
for _, word := range words {
uniqueWords[word] = true
}
// 从集合获取所有元素
var unique []string
for word := range uniqueWords {
unique = append(unique, word)
}
fmt.Println("Unique words:", unique)
}
执行结果:
Unique words: [hello world go]
要点:
uniqueWords[word] = true--- 用 bool 值标记,true 表示元素在集合中- "hello" 和 "world" 出现两次,但 map 键唯一,所以只保留一个
- 从集合提取元素:遍历 map 的键,
for word := range uniqueWords - 集合特性:自动去重、快速判断存在(
uniqueWords[word]返回 true/false) - 遍历顺序不确定:输出可能是
[hello world go]或[go hello world]
5. Map 合并 --- 后者覆盖前者
合并两个 map 时,重复键由后写入的值决定(覆盖):
go
package main
import "fmt"
func main() {
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"b": 3, "c": 4} // "b" 在两个 map 中都存在
merged := make(map[string]int)
// 先复制 map1
for k, v := range map1 {
merged[k] = v // merged: {a:1, b:2}
}
// 再复制 map2("b" 的值被覆盖)
for k, v := range map2 {
merged[k] = v // merged: {a:1, b:3, c:4} --- b:2 被覆盖为 b:3
}
fmt.Println("Merged maps:", merged)
}
执行结果:
Merged maps: map[a:1 b:3 c:4]
要点:
-
合并方法:遍历两个 map,依次写入新 map
-
map1有b:2,map2有b:3,合并后merged["b"] = 3(map2 的值覆盖了 map1 的) -
覆盖规则:后写入的值覆盖先写入的值
-
如果想保留 map1 的值优先,先复制 map2 再复制 map1:
gofor k, v := range map2 { merged[k] = v } // map2 先 for k, v := range map1 { merged[k] = v } // map1 覆盖 → b:2 -
Go 没有内置的 map 合并函数,需要手动遍历合并
知识点总结
| 知识点 | 关键概念 |
|---|---|
| map 值为切片 | map[K][]V --- 一个键对应多个值,nil切片append安全 |
| 嵌套 map | map[K1]map[K2]V --- 必须逐层初始化内层,两层 comma-ok 安全访问 |
| map 值为结构体 | 不能直接修改字段,必须取出→修改→重新赋值;用指针可直接修改 |
| map 模拟集合 | map[K]bool 去重,键唯一自动去重 |
| map 合并 | 依次遍历写入新 map,后者覆盖前者(重复键由最后写入的值决定) |