7.2 Go Map 进阶

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"]
  • 不安全访问的后果:
    • gradebook["English"]["Alice"] --- "English" 不存在,返回 nil 内层 map,写入 panic
    • gradebook["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"] 返回的是值的拷贝,不是引用

  • 正确步骤:

    1. john := people["John"] --- 取出值拷贝
    2. john.Age = 31 --- 修改拷贝的字段
    3. people["John"] = john --- 把修改后的值重新赋值回 map
  • 这个限制只针对值类型 (struct、int 等),如果值是指针类型 则可以直接修改:

    go 复制代码
    people := 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:

    go 复制代码
    for 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,后者覆盖前者(重复键由最后写入的值决定)
相关推荐
JWASX7 小时前
go 学习 - prometheus 指标学习
学习·golang·prometheus
脉动数据行情17 小时前
Go 语言实战|对接美股英伟达 NVDA 实时行情 生产级 WebSocket 方案
开发语言·websocket·golang
JWASX8 小时前
Java 转 go 学习 - dubbo-go(2)
学习·golang
徐小黑ACG9 小时前
Golang 基础02 控制语句
开发语言·后端·golang
徐小黑ACG9 小时前
Golang 基础03 函数
开发语言·后端·golang
JWASX9 小时前
go 学习 - prometheus 指标协程池监控
学习·golang·prometheus
喜欢coding的谢同学10 小时前
Go语言常用设计模式基础和实践案例介绍
开发语言·设计模式·golang
ttwuai1 天前
Go后台管理系统开源项目:4个官方仓库怎么追溯和核验
开发语言·golang·开源
谢亮_vipxieliang1 天前
Go Worker Pool 设计——从原理到生产级实现
开发语言·后端·golang
谢亮_vipxieliang1 天前
Go Channel 高级模式——从底层原理到扇出扇入实战
java·数据库·golang