Go 语言 Map(集合)¶
map 保存键值对,写作 map[K]V。键类型必须可比较,例如布尔、数字、字符串、指针、channel、
数组以及字段均可比较的结构体;切片、map 和函数不能作为键。
创建与基本操作¶
package main
import "fmt"
func main() {
scores := map[string]int{"Alice": 90}
scores["Bob"] = 85
score, ok := scores["Alice"]
fmt.Println(score, ok, len(scores))
delete(scores, "Bob")
clear(scores) // Go 1.21+
}
索引读取返回值类型的零值;第二个返回值表示键是否存在。delete 删除不存在的键是安全的,
clear 会删除所有条目,但不会把变量设为 nil。
nil map 与空 map¶
var nilMap map[string]int
emptyMap := make(map[string]int)
fmt.Println(nilMap["x"], len(nilMap)) // 0 0
fmt.Println(emptyMap["x"], len(emptyMap))
// nilMap["x"] = 1 // panic: assignment to entry in nil map
这是第一个差异示例:nil map 可以读取、查询、删除和遍历,但不能写入;由 make 或字面量创建的
空 map 可以写入。若函数只返回只读空结果,nil map 可能足够;若调用方要写入,应明确初始化。
缺失键与零值¶
ages := map[string]int{"newborn": 0}
a, okA := ages["newborn"]
b, okB := ages["missing"]
fmt.Println(a, okA, b, okB) // 0 true 0 false
这是第二个差异示例:单值读取无法区分“键不存在”和“键存在但值为零”,需要 comma-ok 形式。
计数器可有意利用零值:counts[word]++ 无需先检查键。
map 不是并发安全容器¶
多个 goroutine 只读且期间绝无写入是安全的;只要存在并发写,或读写并发,就必须同步。
未同步访问可能触发数据竞争甚至 fatal error: concurrent map read and map write。
type SafeCounts struct {
mu sync.RWMutex
m map[string]int
}
func (s *SafeCounts) Add(key string) {
s.mu.Lock()
defer s.mu.Unlock()
s.m[key]++
}
通用业务状态通常用 map 加 sync.RWMutex;sync.Map 更适合其文档描述的写一次读多次,
或不同 goroutine 操作不相交键的特殊场景,不是普通 map 的默认替代品。
遍历与引用语义¶
map 遍历顺序未指定。赋值或传参不会复制所有条目,多个变量指向同一 map 数据;修改一个变量的
条目,其他变量可见。若需要独立副本,可用 Go 1.21+ 的 maps.Clone,但值若含指针仍是浅拷贝。
常见错误¶
- 向 nil map 写入条目。
- 忽略
ok,把缺失键误判为真实零值。 - 依赖遍历顺序生成稳定输出。
- 无锁并发读写 map,认为不同键互不影响。
- 取得
m[key]中结构体字段并直接赋值;map 索引结果不可寻址,应取出、修改后再写回。
性能与工程实践¶
已知条目数量时可用 make(map[K]V, n) 提供容量提示,减少增长成本,但它不是硬上限。
键应保持稳定,避免使用巨大结构体增加哈希成本。公开 API 应说明 map 的所有权和可变性;若不能让
调用方修改内部状态,就返回克隆或只暴露查询方法。稳定序列化时收集并排序键,不要依赖运行时顺序。