Files
cs-note/hzh/GolangStar/Go语言基础/Go语言Map.md
T

4.1 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
map
哈希表
2026-06-07 15:00

Go 语言 Map

概述

Map 是 Go 的内置哈希表,提供 O(1) 平均复杂度的键值对操作。本文覆盖创建、增删改查、遍历时序随机性、并发安全等核心要点。

正文

为什么 Go 的 Map 遍历顺序是随机的?

这不是 bug,而是有意设计。Go 的 map 在每次遍历时会随机打乱起始位置,目的是防止程序员依赖遍历顺序——而依赖未定义行为正是很多 bug 的源头。

[!warning] ⚠️ 记住这条规则 永远不要依赖 map 的遍历顺序。 如果需要有序输出,先收集 key 再排序。

创建与初始化

// 方式一:make(空 map)
m := make(map[string]int)

// 方式二:字面量(带初始值)
scores := map[string]int{
    "张三": 95,
    "李四": 88,
}

// 方式三:var 声明(零值为 nil,需 make 后才能用)
var prices map[string]float64
prices = make(map[string]float64)

[!note] 📝 nil map 不能写入 var m map[string]int 创建的 map 是 nil。对它做 m["a"] = 1 会 panic。必须先 make。

基本操作

// 写入 / 更新
m["apple"] = 5.5

// 读取(不存在的 key 返回零值)
price := m["apple"]       // float64 → 0.0(如果 key 不存在)

// 判断 key 是否存在
price, exists := m["apple"]
if exists {
    fmt.Println(price)
} else {
    fmt.Println("不存在")
}

// 删除
delete(m, "apple")

// 获取元素个数
fmt.Println(len(m))

[!tip] 💡 单返回值惯用法 判断 key 是否存在还有一种更简洁的写法:

if price, ok := m["apple"]; ok {
    // key 存在
}

ok 是 Go 中约定俗成的布尔变量名,表示"是否成功"。

嵌套 Map

Map 的值可以是另一个 Map:

studentScores := map[string]map[string]int{
    "张三": {"数学": 95, "英语": 88},
    "李四": {"数学": 90, "英语": 85},
}

// 获取张三的英语成绩
score := studentScores["张三"]["英语"]

[!warning] ⚠️ 需要先初始化内层 map

studentScores["王五"] = make(map[string]int)  // 必须先 make
studentScores["王五"]["数学"] = 92             // 然后才能写入

Map 作为函数参数

Map 是引用类型——传入函数后修改会影响原始 map:

func addScore(scores map[string]int, name string, score int) {
    scores[name] = score  // 直接修改原 map
}

m := map[string]int{"张三": 95}
addScore(m, "李四", 88)
fmt.Println(m)  // map[张三:95 李四:88]

性能优化:预分配容量

如果知道 map 的大致大小,传 cap 可以减少扩容开销:

// 预计约 1000 个元素
m := make(map[string]int, 1000)

[!info] ℹ️ 注意 make(map[T]U, 1000) 中的第二个参数是容量提示(不是强制限制)。map 仍会自动扩容,但预分配可以减少重新分配的次数。

并发安全

普通 map 不是并发安全的。多个 goroutine 同时写会 panic:

fatal error: concurrent map writes

方案一:sync.Mutex

var mu sync.Mutex
m := make(map[string]int)

mu.Lock()
m["key"] = 1
mu.Unlock()

mu.Lock()
val := m["key"]
mu.Unlock()

方案二:sync.Map

适用于读多写少或 key 集合固定的场景:

var sm sync.Map

sm.Store("key", 42)        // 写入
val, ok := sm.Load("key")  // 读取 → (interface{}, bool)
sm.Delete("key")           // 删除

[!note] 📝 sync.Map vs Mutex+map

  • sync.Map 适合 key 集固定、读远多于写的场景
  • 写频繁时,sync.Mutex + map 通常性能更好
  • 具体选择取决于你的读写比例

常见陷阱总结

陷阱 说明 修复
nil map 写入 var m map[K]V 后直接赋值会 panic 先用 make
不存在的 key 返回零值 m["missing"] 返回类型的零值 用 v, ok := m[k] 判断
遍历顺序随机 每次运行顺序不同 需要有序则先排序 key
并发写 panic 多 goroutine 同时写 加锁或用 sync.Map
不能取元素地址 &m["key"] 编译错误 无法直接取 map 值的地址