404 Not Found

404 Not Found


nginx

Map

Map

想象你在查字典:你输入一个单词(key),就能立刻找到它的释义(value)。不需要从第一页翻到最后一页,而是直接定位到目标页。Go 语言中的 map 就是这样一个"字典"——它是一种键值对(key-value)数据结构,能让你通过 key 快速检索 value,时间复杂度为 O(1)。


1. 核心概念

概念 说明
定义 map[KeyType]ValueType,KeyType 必须是可比较类型(不能是 slice、map、func)
创建方式 make(map[K]V)map[K]V{} 字面量
增/改 m[key] = value(key 不存在则新增,存在则覆盖)
v := m[key]
delete(m, key)
comma ok 模式 v, ok := m[key],ok 为 false 表示 key 不存在
遍历 for k, v := range m(顺序随机,不可依赖)
长度 len(m)
⚠️ 注意:map 是引用类型,赋值和传参传递的是引用,不会复制数据。


2. 基本语法/用法

创建 map

GO
// 方式一:使用 make 创建
m1 := make(map[string]int)

// 方式二:字面量创建并初始化
m2 := map[string]int{
    "apple":  5,
    "banana": 3,
}

// 方式三:声明后赋值
var m3 map[string]int        // 此时 m3 是 nil,不能直接赋值
m3 = make(map[string]int)    // 先初始化
m3["cherry"] = 7             // 再赋值
💡 提示:var m map[K]V 声明的 map 是 nil不能写入(会 panic),但可以读取(返回零值)和 len()(返回 0)。

增删改查

GO
m := map[string]int{"a": 1, "b": 2}

// 增
m["c"] = 3

// 改
m["a"] = 10

// 查
v := m["a"]  // v = 10

// 删
delete(m, "b")
💡 提示:删除一个不存在的 key 不会报错,也不会发生任何事情。

Comma Ok 模式

GO
m := map[string]int{"x": 42}

v, ok := m["x"]  // v = 42, ok = true
v, ok = m["y"]   // v = 0, ok = false(返回 int 的零值)

if _, exists := m["z"]; !exists {
    fmt.Println("key 'z' 不存在")
}
💡 提示:如果不关心 value,可以用 _ 忽略:_, ok := m[key]


3. 示例代码

示例 1:基础用法(难度⭐)

创建一个学生成绩表,进行增删改查操作。

GO
package main

import "fmt"

func main() {
    // 创建学生成绩 map
    scores := map[string]int{
        "Alice": 90,
        "Bob":   85,
    }

    // 新增一个学生
    scores["Charlie"] = 92

    // 修改 Bob 的成绩
    scores["Bob"] = 88

    // 查询 Alice 的成绩
    fmt.Println("Alice 的成绩:", scores["Alice"])

    // 删除 Charlie
    delete(scores, "Charlie")

    // 遍历所有学生成绩
    for name, score := range scores {
        fmt.Printf("%s: %d\n", name, score)
    }

    fmt.Println("学生人数:", len(scores))
}
▶ 试一试

输出示例(遍历顺序可能不同):

TEXT
Alice 的成绩: 90
Bob: 88
Alice: 90
学生人数: 2

示例 2:进阶用法(难度⭐⭐)

使用 comma ok 模式安全查询,遍历 map,以及嵌套 map 的使用。

GO
package main

import "fmt"

func main() {
    // ========== comma ok 模式 ==========
    fruits := map[string]int{
        "apple":  5,
        "banana": 3,
    }

    // 安全查询
    if count, ok := fruits["apple"]; ok {
        fmt.Printf("apple 有 %d 个\n", count)
    }

    if count, ok := fruits["grape"]; !ok {
        fmt.Println("grape 不存在,将添加到列表中")
        fruits["grape"] = 10
    }

    // ========== range 遍历 ==========
    fmt.Println("\n所有水果:")
    for fruit, count := range fruits {
        fmt.Printf("  %s: %d\n", fruit, count)
    }

    // ========== 嵌套 map(map of maps)==========
    // 班级 -> 学生 -> 成绩
    classScores := map[string]map[string]int{
        "一班": {
            "Alice": 90,
            "Bob":   85,
        },
        "二班": {
            "Charlie": 92,
            "Diana":   88,
        },
    }

    // 查询嵌套 map
    if class, ok := classScores["一班"]; ok {
        if score, ok := class["Alice"]; ok {
            fmt.Printf("\n一班 Alice 的成绩: %d\n", score)
        }
    }

    // 遍历嵌套 map
    fmt.Println("\n所有班级成绩:")
    for class, students := range classScores {
        fmt.Printf("  %s:\n", class)
        for name, score := range students {
            fmt.Printf("    %s: %d\n", name, score)
        }
    }
}
▶ 试一试

输出

TEXT
apple 有 5 个
grape 不存在,将添加到列表中

所有水果:
  apple: 5
  banana: 3
  grape: 10

一班 Alice 的成绩: 90

所有班级成绩:
  一班:
    Alice: 90
    Bob: 85
  二班:
    Charlie: 92
    Diana: 88

示例 3:综合应用(难度⭐⭐⭐)

实现一个词频统计器,并基于 map 进行数据处理(排序输出、找出高频词)。

GO
package main

import (
    "fmt"
    "sort"
    "strings"
)

// WordCounter 统计文本中每个单词出现的次数
func WordCounter(text string) map[string]int {
    // 转为小写并按空格分割
    words := strings.Fields(strings.ToLower(text))

    // 创建词频 map
    freq := make(map[string]int)

    for _, word := range words {
        // 去除标点符号(简单处理)
        word = strings.Trim(word, ".,!?;:\"'")
        if word != "" {
            freq[word]++
        }
    }

    return freq
}

// TopN 返回频率最高的 N 个单词(按频率降序)
func TopN(freq map[string]int, n int) []string {
    // 将 map 转换为可排序的切片
    type wordFreq struct {
        word  string
        count int
    }

    // 构造切片
    pairs := make([]wordFreq, 0, len(freq))
    for w, c := range freq {
        pairs = append(pairs, wordFreq{w, c})
    }

    // 按频率降序排序
    sort.Slice(pairs, func(i, j int) bool {
        if pairs[i].count == pairs[j].count {
            return pairs[i].word < pairs[j].word // 频率相同按字母排序
        }
        return pairs[i].count > pairs[j].count
    })

    // 取前 N 个
    if n > len(pairs) {
        n = len(pairs)
    }

    result := make([]string, n)
    for i := 0; i < n; i++ {
        result[i] = fmt.Sprintf("%s(%d)", pairs[i].word, pairs[i].count)
    }

    return result
}

// MergeFreq 合并两个词频 map
func MergeFreq(a, b map[string]int) map[string]int {
    result := make(map[string]int)

    // 复制 a 的数据
    for k, v := range a {
        result[k] = v
    }

    // 累加 b 的数据
    for k, v := range b {
        result[k] += v
    }

    return result
}

func main() {
    text1 := "Go is great. Go is fast. Go is easy to learn."
    text2 := "I love Go. Go makes programming fun and easy."

    // 统计词频
    freq1 := WordCounter(text1)
    freq2 := WordCounter(text2)

    fmt.Println("文本1 词频:")
    for word, count := range freq1 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    fmt.Println("\n文本2 词频:")
    for word, count := range freq2 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    // 合并词频
    merged := MergeFreq(freq1, freq2)

    // 找出频率最高的 5 个单词
    fmt.Println("\n合并后 Top 5 高频词:")
    top5 := TopN(merged, 5)
    for i, item := range top5 {
        fmt.Printf("  %d. %s\n", i+1, item)
    }

    // 统计总词数
    totalWords := 0
    for _, count := range merged {
        totalWords += count
    }
    fmt.Printf("\n总词数: %d,不同单词数: %d\n", totalWords, len(merged))
}
▶ 试一试

输出示例

TEXT
文本1 词频:
  go: 3
  is: 3
  great: 1
  fast: 1
  easy: 1
  to: 1
  learn: 1

文本2 词频:
  i: 1
  love: 1
  go: 2
  makes: 1
  programming: 1
  fun: 1
  and: 1
  easy: 1

合并后 Top 5 高频词:
  1. go(5)
  2. is(3)
  3. easy(2)
  4. and(1)
  5. fast(1)

总词数: 18,不同单词数: 12

3. 常见应用场景

场景一:缓存/快速查找

用 map 实现一个简单的内存缓存,避免重复计算。

GO
package main

import "fmt"

// 斐波那契数列(带缓存)
var cache = map[int]int{0: 0, 1: 1}

func fib(n int) int {
    // 先查缓存
    if val, ok := cache[n]; ok {
        return val
    }

    // 缓存未命中,计算并存入缓存
    result := fib(n-1) + fib(n-2)
    cache[n] = result
    return result
}

func main() {
    for i := 0; i <= 10; i++ {
        fmt.Printf("fib(%d) = %d\n", i, fib(i))
    }
    fmt.Println("\n缓存内容:", cache)
}

场景二:分组统计

用 map 对数据进行分组统计。

GO
package main

import "fmt"

func main() {
    // 学生列表:名字 -> 班级
    students := map[string]string{
        "Alice":   "一班",
        "Bob":     "二班",
        "Charlie": "一班",
        "Diana":   "二班",
        "Eve":     "一班",
    }

    // 按班级分组
    groups := make(map[string][]string)
    for name, class := range students {
        groups[class] = append(groups[class], name)
    }

    // 输出分组结果
    for class, members := range groups {
        fmt.Printf("%s (%d人): %v\n", class, len(members), members)
    }
}

输出

TEXT
一班 (3人): [Alice Charlie Eve]
二班 (2人): [Bob Diana]

❓ 常见问题

Q1:为什么遍历 map 的顺序每次都不一样?

Go 语言故意让 map 的遍历顺序随机化。这是为了防止开发者依赖 map 的遍历顺序,因为在并发场景下 map 的内部结构可能发生变化。如果需要有序遍历,应该先将 key 收集到切片中,排序后再遍历:

GO
keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, m[k])
}

Q2:map 是线程安全的吗?

不是。 Go 的 map 不是并发安全的,多个 goroutine 同时读写同一个 map 会导致 panic。解决方案:

GO
// 使用 sync.Map
var m sync.Map
m.Store("key", "value")
v, ok := m.Load("key")

Q3:map 的 key 可以用哪些类型?

map 的 key 必须是可比较类型,包括:

不能作为 key 的类型:slicemapfunc

Q4:如何判断一个 map 是否包含某个 key?

使用 comma ok 模式:

GO
if _, ok := m[key]; ok {
    // key 存在
} else {
    // key 不存在
}

不要通过判断 value 是否为零值来判断 key 是否存在,因为零值也可能是合法的 value。


📖 小节


📝 作业

练习 1(⭐)

创建一个 map 存储 5 个编程语言及其发明年份,然后:

  1. 添加 2 门新语言
  2. 修改一门语言的年份
  3. 删除一门语言
  4. 使用 comma ok 模式查询一门语言是否存在
  5. 遍历并打印所有内容

练习 2(⭐⭐)

实现一个简单的通讯录程序:

  1. 定义 map[string][]string,key 为联系人姓名,value 为电话号码列表
  2. 实现添加联系人、添加电话号码、查找联系人、删除联系人功能
  3. 实现按首字母分组显示所有联系人
GO
// 预期输出示例:
// A: Alice - [13800001111, 13900002222]
// B: Bob - [13700003333]

练习 3(⭐⭐⭐)

实现一个学生成绩管理系统

  1. 使用嵌套 map[string]map[string]float64(班级 -> 学生 -> 成绩)
  2. 实现功能:添加成绩、查询某学生所有科目成绩、计算某班级平均分
  3. 实现功能:找出所有班级中每个科目的最高分学生
  4. 将结果按格式化表格输出
GO
// 预期输出示例:
// ========== 班级平均分 ==========
// 一班: 87.5
// 二班: 91.2
//
// ========== 各科最高分 ==========
// 数学: Alice (98.0)
// 英语: Bob (95.0)

下一课

👉 07-struct - 结构体

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏