7.1 Go Map 基础

7.1 Go Map 基础

1. Map 基础 --- 创建、访问、删除

Map 是键值对的无序集合,类似其他语言的字典/哈希表:

go 复制代码
package main

import "fmt"

func main() {
    // make 创建空 map
    ages := make(map[string]int)

    // map 字面量创建并初始化
    colors := map[string]string{
        "red":   "#FF0000",
        "green": "#00FF00",
        "blue":  "#0000FF",
        "white": "#FFFFFF",
        "black": "#000000",
    }

    // 添加键值对
    ages["Alice"] = 25
    ages["Bob"] = 30
    ages["Charlie"] = 35

    // 访问值
    aliceAge := ages["Alice"] // 25

    // comma-ok 模式:检查键是否存在
    bobAge, exists := ages["Bob"]
    if exists {
        fmt.Printf("Bob's age: %d\n", bobAge)
    }

    // 访问不存在的键:返回零值(不会报错!)
    unknown := ages["Unknown"] // 返回 0(int 的零值)
    fmt.Printf("Unknown person's age (zero value): %d\n", unknown)

    // 删除键
    delete(ages, "Alice") // 删除后 ages 只有 Bob 和 Charlie

    // 获取长度
    length := len(ages) // 2

    // nil map:var 声明未初始化
    var nilMap map[string]int // nilMap == nil
    // nilMap["test"] = 1  // 会 panic!nil map 不能写入
    if nilMap == nil {
        fmt.Println("nilMap is nil - cannot assign to it")
    }

    fmt.Println("Ages map:", ages)
    fmt.Println("Colors map:", colors)
    fmt.Printf("Alice's age: %d\n", aliceAge)
    fmt.Printf("Map length: %d\n", length)
}

执行结果:

复制代码
Bob's age: 30
Unknown person's age (zero value): 0
nilMap is nil - cannot assign to it
Ages map: map[Bob:30 Charlie:35]
Colors map: map[black:#000000 blue:#0000FF green:#00FF00 red:#FF0000 white:#FFFFFF]
Alice's age: 25
Map length: 2

要点

  • make(map[string]int) --- 创建空 map,可以写入
  • map[string]string{...} --- 字面量创建,直接初始化
  • ages["Alice"] = 25 --- 添加或更新键值对
  • ages["Unknown"] 返回 0 --- 不存在的键返回值类型的零值,不会 panic
  • 这就是为什么需要 comma-ok 模式:value, ok := ages["Bob"]ok=true 表示键存在
  • 如果只用 ages["Bob"],无法区分"Bob的年龄是0"和"Bob不存在"
  • delete(ages, "Alice") --- 删除键值对,键不存在时 delete 不报错
  • var nilMap map[string]int --- nil map,读取返回零值,但写入会 panic
  • 必须用 make() 或字面量初始化才能写入

2. Map 遍历 --- range 与排序遍历

Map 遍历顺序是随机的,需要有序遍历时必须先排序键:

go 复制代码
package main

import (
    "fmt"
    "sort"
)

func main() {
    grades := map[string]int{
        "Alice": 92, "Bob": 85, "Charlie": 78, "Diana": 96, "Eve": 88,
    }

    inventory := map[string]int{
        "apples": 50, "bananas": 30, "oranges": 25,
    }

    // 键 + 值遍历(顺序随机!)
    fmt.Println("Student grades:")
    for name, grade := range grades {
        fmt.Printf("  %s: %d\n", name, grade)
    }

    // 只遍历键
    fmt.Println("\nStudent names (keys only):")
    for name := range grades {
        fmt.Printf("  %s\n", name)
    }

    // 只遍历值
    fmt.Println("\nGrade values only:")
    for _, grade := range grades {
        fmt.Printf("  %d\n", grade)
    }

    // 求和与平均值
    var sum int
    var count int
    for _, grade := range grades {
        sum += grade
        count++
    }
    average := float64(sum) / float64(count)
    fmt.Printf("\nTotal sum: %d, Average: %.2f\n", sum, average)

    // 找最高分
    var maxGrade int
    var topStudent string
    for name, grade := range grades {
        if grade > maxGrade {
            maxGrade = grade
            topStudent = name
        }
    }
    fmt.Printf("Top student: %s with grade %d\n", topStudent, maxGrade)

    // 有序遍历:先收集键 → 排序 → 按排序后的键访问
    var keys []string
    for product := range inventory {
        keys = append(keys, product)
    }
    sort.Strings(keys)

    fmt.Println("\nInventory (sorted by product name):")
    for _, product := range keys {
        fmt.Printf("  %s: %d\n", product, inventory[product])
    }

    // 条件计数
    lowStock := 0
    threshold := 30
    for _, stock := range inventory {
        if stock <= threshold {
            lowStock++
        }
    }
    fmt.Printf("\nProducts with low stock (<= %d): %d\n", threshold, lowStock)
}

执行结果:

复制代码
Student grades:
  Alice: 92
  Bob: 85
  Charlie: 78
  Diana: 96
  Eve: 88

Student names (keys only):
  Charlie
  Diana
  Eve
  Alice
  Bob

Grade values only:
  92
  85
  78
  96
  88

Total sum: 439, Average: 87.80
Top student: Diana with grade 96

Inventory (sorted by product name):
  apples: 50
  bananas: 30
  oranges: 25

Products with low stock (<= 30): 2

要点

  • for name, grade := range grades --- 遍历顺序不确定,每次运行可能不同
  • for name := range grades --- 只遍历键(name 是键,不是值!)
  • for _, grade := range grades --- _ 忽略键,只遍历值
  • 有序遍历模式:收集键 → sort.Strings(keys) → 按排序后的键访问 inventory[product]
  • 这是 Go 中 map 有序遍历的标准模式(Go 没有内置的有序 map)
  • 统计计算:求和、平均值、最大值、条件计数,与切片遍历逻辑一致

3. Map 模式 --- 计数器(词频统计)

map[key]++ 是最常用的计数模式,零值特性让代码极简:

go 复制代码
package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "hello world hello go programming go is awesome"

    // 词频计数:map[string]int + wordCounts[word]++
    wordCounts := make(map[string]int)
    words := strings.Fields(text) // 按空格分割字符串

    for _, word := range words {
        wordCounts[word]++ // 关键!不需要判断键是否存在
        // 不存在时 wordCounts[word] = 0(零值),++ 后变成 1
        // 已存在时 wordCounts[word] 直接 ++ 增加
    }

    fmt.Println("Word counts:")
    for word, count := range wordCounts {
        fmt.Printf("  %s: %d\n", word, count)
    }
}

执行结果:

复制代码
Word counts:
  hello: 2
  world: 1
  go: 2
  programming: 1
  is: 1
  awesome: 1

要点

  • wordCounts[word]++ --- 这是 Go map 计数的核心技巧:
    • 首次遇到某词:wordCounts["hello"] 返回零值 0,++ 后变成 1
    • 再次遇到:wordCounts["hello"] = 1,++ 后变成 2
    • 不需要判断键是否存在,零值特性自动处理
  • strings.Fields(text) --- 按空格分割字符串为切片,比 strings.Split(text, " ") 更智能(处理多个连续空格)
  • 这是 Go 中最常用的 map 模式:统计频率、计数、投票等

4. Map 模式 --- 分组(Group by)

map[key][]value 实现分组:同一键的值追加到切片:

go 复制代码
package main

import "fmt"

func main() {
    students := []struct {
        Name  string
        Grade string
        Score int
    }{
        {"Alice", "A", 95}, {"Bob", "B", 87}, {"Charlie", "A", 92},
        {"Diana", "B", 89}, {"Eve", "A", 98}, {"Frank", "C", 75}, {"Grace", "B", 85},
    }

    // 按等级分组:map[string][]string
    gradeGroups := make(map[string][]string)
    for _, student := range students {
        gradeGroups[student.Grade] = append(gradeGroups[student.Grade], student.Name)
    }

    fmt.Println("Students grouped by grade:")
    for grade, names := range gradeGroups {
        fmt.Printf("  Grade %s: %v\n", grade, names)
    }
}

执行结果:

复制代码
Students grouped by grade:
  Grade A: [Alice Charlie Eve]
  Grade B: [Bob Diana Grace]
  Grade C: [Frank]

要点

  • make(map[string][]string) --- 值是切片类型,实现"一个键对应多个值"
  • append(gradeGroups[student.Grade], student.Name) --- 零值是 nil 切片,append 自动分配底层数组
    • 首次遇到 Grade "A":gradeGroups["A"] 是 nil 切片,append(nil, "Alice")"Alice"
    • 再次遇到 Grade "A":append(["Alice"], "Charlie")"Alice", "Charlie"
    • 不需要初始化空切片,nil 切片 append 是安全的
  • 分组是数据分析的基本操作:按部门分组员工、按日期分组事件等

5. Map 模式 --- 集合操作(Set)

Go 没有 Set 类型,用 map[key]boolmap[key]struct{} 模拟集合:

go 复制代码
package main

import "fmt"

func main() {
    set1 := map[string]bool{"apple": true, "banana": true, "cherry": true}
    set2 := map[string]bool{"banana": true, "date": true, "cherry": true}

    // 交集:在 set1 且在 set2 中
    intersection := make(map[string]bool)
    for key := range set1 {
        if set2[key] { // set2[key] 返回 true = 存在,false = 不存在
            intersection[key] = true
        }
    }

    // 并集:所有元素
    union := make(map[string]bool)
    for key := range set1 { union[key] = true }
    for key := range set2 { union[key] = true }

    // 差集:在 set1 但不在 set2 中
    difference := make(map[string]bool)
    for key := range set1 {
        if !set2[key] { // 不在 set2 中
            difference[key] = true
        }
    }

    fmt.Println("Set operations:")
    fmt.Printf("Set1: %v\n", getKeys(set1))
    fmt.Printf("Set2: %v\n", getKeys(set2))
    fmt.Printf("Intersection: %v\n", getKeys(intersection))
    fmt.Printf("Union: %v\n", getKeys(union))
    fmt.Printf("Difference (set1 - set2): %v\n", getKeys(difference))
}

func getKeys(m map[string]bool) []string {
    var keys []string
    for key := range m { keys = append(keys, key) }
    return keys
}

执行结果:

复制代码
Set operations:
Set1: [apple banana cherry]
Set2: [banana date cherry]
Intersection: [banana cherry]
Union: [banana cherry date apple]
Difference (set1 - set2): [apple]

要点

  • map[string]bool --- bool 值固定为 true,键本身就是集合元素

  • set2[key] --- 存在返回 true,不存在返回 false(零值),天然适合集合的"包含"判断

  • 交集:遍历 set1,检查键是否也在 set2 中

  • 并集:把两个 map 的所有键加入新 map(重复键自动合并)

  • 差集:遍历 set1,排除在 set2 中存在的键

  • 更节省内存的写法:map[string]struct{} --- struct{} 占0字节,但判断存在需要 comma-ok:

    go 复制代码
    _, exists := set[key] // 用 comma-ok 而非零值判断

6. Map 模式 --- 缓存/记忆化(Memoization)

map 作为缓存,避免重复计算:

go 复制代码
package main

import "fmt"

func fibonacciMemo(n int, cache map[int]int) int {
    if n <= 1 {
        return n
    }
    // 先查缓存
    if value, exists := cache[n]; exists {
        return value // 已计算过,直接返回
    }
    // 未缓存,递归计算并存入缓存
    result := fibonacciMemo(n-1, cache) + fibonacciMemo(n-2, cache)
    cache[n] = result
    return result
}

func main() {
    fibCache := make(map[int]int)

    fmt.Println("Fibonacci with memoization:")
    for i := 0; i <= 10; i++ {
        result := fibonacciMemo(i, fibCache)
        fmt.Printf("fib(%d) = %d\n", i, result)
    }
    fmt.Printf("Cache after calculations: %v\n", fibCache)
}

执行结果:

复制代码
Fibonacci with memoization:
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
Cache after calculations: map[2:1 3:2 4:3 5:5 6:8 7:13 8:21 9:34 10:55]

要点

  • cache map[int]int --- 参数缓存,n 为键,fib(n) 为值
  • if value, exists := cache[n]; exists --- 先查缓存,存在则直接返回(避免重复计算)
  • cache[n] = result --- 计算后存入缓存,下次相同 n 不再递归
  • 对比朴素递归:O(2^n) → 记忆化后 O(n),性能大幅提升
  • 缓存中存储了 fib(2) 到 fib(10) 的结果(0 和 1 是基例,未存缓存)
  • 记忆化是优化递归的通用技术,适用于任何有重复子问题的算法

7. Map 模式 --- 默认值与频率分析

go 复制代码
package main

import "fmt"

// 获取配置值,不存在时返回默认值
func getConfigValue(config map[string]string, key, defaultValue string) string {
    if value, exists := config[key]; exists {
        return value
    }
    return defaultValue
}

func main() {
    // 默认值模式
    userConfig := map[string]string{"theme": "dark", "lang": "en"}

    theme := getConfigValue(userConfig, "theme", "light")    // 存在,返回 "dark"
    language := getConfigValue(userConfig, "lang", "en")     // 存在,返回 "en"
    timeout := getConfigValue(userConfig, "timeout", "30s")  // 不存在,返回默认 "30s"

    fmt.Printf("Theme: %s\n", theme)
    fmt.Printf("Language: %s\n", language)
    fmt.Printf("Timeout: %s (default used)\n", timeout)

    // 频率分析:找最频繁的数
    numbers := []int{1, 2, 3, 2, 1, 3, 1, 4, 5, 4, 1}
    frequency := make(map[int]int)
    for _, num := range numbers {
        frequency[num]++ // 计数器模式
    }

    var mostFrequent int
    var maxCount int
    for num, count := range frequency {
        if count > maxCount {
            mostFrequent = num
            maxCount = count
        }
    }
    fmt.Printf("\nMost frequent number: %d (appears %d times)\n", mostFrequent, maxCount)
}

执行结果:

复制代码
Theme: dark
Language: en
Timeout: 30s (default used)
Most frequent number: 1 (appears 4 times)

要点

  • 默认值模式:getConfigValue(config, key, default) --- 用 comma-ok 检查键是否存在,不存在返回默认值
  • 频率分析 = 计数器模式 + 找最大值:先用 frequency[num]++ 统计频次,再遍历 map 找最高频
  • 1 出现 4 次(最多),2 出现 2 次,3 出现 2 次,4 出现 2 次,5 出现 1 次
  • 实际场景:配置管理、最热商品、最活跃用户等

8. Map 模式 --- 嵌套 map(Map of Maps)

map[key1]map[key2]value 实现二维数据结构:

go 复制代码
package main

import "fmt"

func main() {
    // 嵌套 map:学生 → 科目 → 成绩
    studentGrades := make(map[string]map[string]int)

    // 必须初始化每个内层 map!
    studentGrades["Alice"] = make(map[string]int)
    studentGrades["Bob"] = make(map[string]int)

    studentGrades["Alice"]["Math"] = 95
    studentGrades["Alice"]["Science"] = 92
    studentGrades["Alice"]["English"] = 88

    studentGrades["Bob"]["Math"] = 87
    studentGrades["Bob"]["Science"] = 90
    studentGrades["Bob"]["English"] = 85

    // 嵌套 range 遍历
    fmt.Println("Student grades by subject:")
    for student, subjects := range studentGrades {
        fmt.Printf("%s:\n", student)
        for subject, grade := range subjects {
            fmt.Printf("  %s: %d\n", subject, grade)
        }
    }
}

执行结果:

复制代码
Student grades by subject:
Bob:
  Science: 90
  English: 85
  Math: 87
Alice:
  Math: 95
  Science: 92
  English: 88

要点

  • make(map[string]map[string]int) --- 外层 map 的值是内层 map
  • 必须手动初始化内层 mapstudentGrades["Alice"] = make(map[string]int)
    • 如果不初始化:studentGrades["Alice"]["Math"] = 95 会 panic(内层 map 是 nil)
  • 嵌套 range 遍历:外层遍历学生,内层遍历科目和成绩
  • 嵌套 map 适合多维度数据:学生×科目、城市×月份、产品×属性等
  • 注意:Go 的 map 不能直接做 map 的值类型字面量初始化,必须逐层 make

知识点总结

知识点 关键概念
Map 创建 make(map[K]V)map[K]V{...} 字面量
访问与零值 不存在的键返回零值,用 comma-ok 区分"零值"和"不存在"
nil map var 声明是 nil,读取返回零值,写入 panic
delete delete(map, key) 删除键,不存在不报错
遍历 range 顺序随机,有序遍历需先排序键
计数器 map[key]++ 零值自动为0,不需要判断键是否存在
分组 map[K][]V 同一键的值追加到切片,nil切片append安全
集合 map[K]bool 模拟 Set,支持交集/并集/差集
缓存/记忆化 map 存储计算结果,避免重复计算
默认值 comma-ok 检查键,不存在返回默认值
嵌套 map map[K1]map[K2]V 必须逐层 make 初始化内层
相关推荐
运维开发笔记1 小时前
7.2 Go Map 进阶
golang
西瓜很甜哟11 小时前
golang每日十题第2期
golang
程序员小八77715 小时前
Go 语言特性
开发语言·后端·golang
X1A0RAN20 小时前
pytest 动态报告:数据可视化篇 —— 用 Golang + Echo 打造自动化测试任务可视化平台
信息可视化·golang·pytest
圣殿骑士-Khtangc1 天前
Go-sync包并发原语详解从Mutex到Once的深度应用
golang
灯澜忆梦1 天前
【基于GO的Web开发15】gin路由和路由组
前端·后端·golang·gin
xcLeigh1 天前
Go入门:rune与byte的区别和使用场景
android·javascript·golang
深念Y1 天前
数据库层设计的取舍:ORM 便利性与手写 SQL 的安全性权衡
java·数据库·sql·golang·框架·语言·ome
深念Y1 天前
Makefile vs go run:Go 项目构建方式对比
java·开发语言·golang·k8s·编译·流水线·cicd