08-Map 映射:键值对的高效存储

0 阅读4分钟

🔖 Go 语言学习教程 · 第 08 篇

📌 本系列基于 Go 1.23 编写,代码兼容 Go 1.21+

全文约 3500 字 | 阅读约 10 分钟


欢迎来到第八篇 👋

上一篇我们学习了数组和切片。今天来学习 Go 的另一种重要数据结构——Map(映射),也就是其他语言中的字典或哈希表。

Map 是键值对的集合,提供了 O(1) 的查找性能。让我们开始吧

✅ 上一篇练习题答案

以下是上一篇练习题的参考答案,欢迎在评论区分享你的解法~

1. 动手实践:创建容量为 4 的切片,append 到容量 16

s := make([]int, 0, 4)
for i := 1; i <= 16; i++ {
    s = append(s, i)
    fmt.Printf("len=%d, cap=%d
", len(s), cap(s))
}

扩容规律:cap < 256 时翻倍(4→8→16),cap >= 256 时增长 25%。

2. 切片操作:反转整数切片

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

3. 思考题:var s []int 和 s := []int{} 的区别

  • var s []int:nil 切片,len=0, cap=0,底层数组为 nil
  • s := []int{}:空切片,len=0, cap=0,但底层数组非 nil

影响:nil 切片 json.Marshal 输出 null,空切片输出 []。

4. 拓展阅读

Go Blog Go Slices: usage and internals 详细讲解了切片的底层结构和扩容机制。 🚀


一、Map 的创建与初始化

声明与创建

// 声明(nil map,不能直接赋值)
var m1 map[string]int

// make 创建(必须用 make 初始化后才能使用)
m2 := make(map[string]int)

// 字面量初始化
m3 := map[string]int{
	"Go":     95,
	"Rust":   90,
	"Python": 85,
}

💡 nil map 不能写入,会 panic!必须用 make 或字面量初始化。

var m map[string]int
m["key"] = 1  // panic: assignment to entry in nil map

📖 二、Map 的基本操作

增删改查

m := make(map[string]int)

// 增/改
m["Go"] = 95
m["Rust"] = 90
m["Go"] = 98  // 覆盖旧值

// 查
score := m["Go"]       // 98
score2 := m["Java"]    // 0(不存在的键返回零值)

// 安全查找(推荐)
score, ok := m["Go"]
if ok {
	fmt.Println("找到了:", score)
}

// 删
delete(m, "Rust")

// 长度
fmt.Println(len(m))  // 1

判断键是否存在

// 正确方式:用两个返回值
if score, ok := m["Go"]; ok {
	fmt.Println("Go 的分数是:", score)
} else {
	fmt.Println("Go 不在 Map 中")
}

三、遍历 Map

m := map[string]int{"Go": 95, "Rust": 90, "Python": 85}

// range 遍历(顺序不保证)
for k, v := range m {
	fmt.Printf("%s: %d\n", k, v)
}

// 只遍历键
for k := range m {
	fmt.Println(k)
}

// 只遍历值
for _, v := range m {
	fmt.Println(v)
}

💡 Map 遍历顺序是随机的!每次运行结果可能不同。如果需要有序遍历,先提取键再排序。


四、Map 是引用类型

Map 是引用类型,传递时不会拷贝底层数据:

func update(m map[string]int) {
	m["Go"] = 100  // 修改会影响原 Map
}

m := map[string]int{"Go": 95}
update(m)
fmt.Println(m["Go"])  // 100 ← 被修改了!

这与切片类似,Map 变量本质上是一个指向底层哈希表的指针。


五、Map 的嵌套

// Map 的 value 可以是另一个 Map
students := map[string]map[string]int{
	"张三": {"数学": 95, "英语": 88},
	"李四": {"数学": 90, "英语": 92},
}

// 访问嵌套值
fmt.Println(students["张三"]["数学"])  // 95

// 添加新学生(需要先初始化内部 Map)
students["王五"] = map[string]int{"数学": 85, "英语": 90}

📝 六、本篇小结

要点总结
创建必须用 make 或字面量,nil map 不能写入
查找用 v, ok := m[key] 判断键是否存在
删除delete(m, key)
遍历range 遍历,顺序随机
引用类型传递时不拷贝,修改会影响原 Map
嵌套value 可以是 Map、切片等任意类型

💬 一句话总结:

Map 是键值对集合,用 make 初始化,用 v, ok := m[key] 安全查找,遍历顺序随机,是引用类型。


✏️ 七、练习题

  1. ️ 动手实践:创建一个 map[string][]string,存储每个人的爱好列表,并遍历打印。
  2. 🔧 词频统计:用 Map 统计一段英文文本中每个单词出现的次数。
  3. 🤔 思考题:为什么 Go 的 Map 遍历顺序是随机的?这是设计缺陷还是有意为之?
  4. 📚 拓展阅读:查阅 Go Blog 上的 Go Maps in Action。

💡 练习题答案会在下一篇中公布,欢迎在评论区分享你的答案~


关注我,一起学编程

如果本文对你有帮助,欢迎 点赞、在看、转发、关注。