Go 语言 Map(集合)
声明、增删查改、comma-ok、并发安全、以及有序遍历。
Key Pointsmap 必须 `make` 或字面量初始化才能写入;并发读写会直接 panic。
创建与读写
Go
// 三种创建方式
var m1 map[string]int // nil,只能读不能写
m2 := make(map[string]int)
m3 := map[string]int{"a": 1}
m2["x"] = 10
m2["y"] = 20
fmt.Println(m2["x"], len(m2))
delete(m2, "x")
fmt.Println(len(m2))
// m1["k"] = 1 // panic: assignment to entry in nil map
fmt.Println(m1["anything"]) // 读 nil map 返回零值,安全Output
10 2
1
0comma-ok
Go
m := map[string]int{"a": 1}
// 区分「值不存在」和「值就是零」
v := m["b"]
fmt.Println(v) // 0 —— 但 b 不存在
v, ok := m["b"]
fmt.Println(v, ok) // 0 false
v, ok = m["a"]
fmt.Println(v, ok) // 1 true
// 常见写法
if v, ok := m["a"]; ok {
fmt.Println("存在,值为", v)
}Output
0
0 false
1 true
存在,值为 1This is Go's "two-value" idiom, also used for type assertions v, ok := x.(T) and channel receives v, ok := <-ch. Seeing , ok means "did it work?".
遍历
Go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Println(k, v)
}
// 只要键
for k := range m {
_ = k
}
// 只要值
for _, v := range m {
_ = v
}
// 按键排序遍历
keys := slices.Sorted(maps.Keys(m))
for _, k := range keys {
fmt.Println(k, m[k])
}Output
a 1
b 2
c 3
a 1
b 2
c 3并发不安全
Go
m := make(map[int]int)
for i := 0; i < 100; i++ {
go func(n int) {
m[n] = n // 并发写 → fatal error: concurrent map writes
}(i)
}
time.Sleep(time.Second)Output
fatal error: concurrent map writes
goroutine 18 [running]:
main.main.func1(...)Go maps have no built-in locking. Concurrent access triggers a runtime concurrent map read and map write fatal error — not a recoverable panic; the process dies.
三种解法
Go
// 1. 加读写锁(最常用)
var (
mu sync.RWMutex
m = map[string]int{}
)
func get(k string) int {
mu.RLock()
defer mu.RUnlock()
return m[k]
}
func set(k string, v int) {
mu.Lock()
defer mu.Unlock()
m[k] = v
}
// 2. sync.Map:读多写少、键集合稳定的场景
var sm sync.Map
sm.Store("a", 1)
v, ok := sm.Load("a")
// 3. 分片锁:极端高并发时按 key 哈希到不同锁| Approach | Best for |
|---|---|
sync.RWMutex + map | 绝大多数场景 |
sync.Map | 读远多于写、键基本固定(如缓存) |
| 分片锁 | 百万级 QPS 的热点 map |
sync.Map is not "a faster map". Its access goes through interfaces with assertion cost, so it is slower than RWMutex + map in ordinary use. Only worth it when reads vastly outnumber writes.常用操作
Go
m := map[string]int{"a": 1}
m["b"] = 2 // 增
_ = m["a"] // 查
delete(m, "b") // 删
_, exists := m["b"] // 判断存在
// 计数
counts := map[rune]int{}
for _, r := range "hello" {
counts[r]++
}
fmt.Println(counts)
// 键值反转
rev := make(map[int]string, len(counts))
for k, v := range counts {
rev[v] = string(k)
}
fmt.Println(len(rev))Output
map[101:1 104:1 108:2 111:1]
3创建与读写
Go
// 三种创建方式
var m1 map[string]int // nil,只能读不能写
m2 := make(map[string]int)
m3 := map[string]int{"a": 1}
m2["x"] = 10
m2["y"] = 20
fmt.Println(m2["x"], len(m2))
delete(m2, "x")
fmt.Println(len(m2))
// m1["k"] = 1 // panic: assignment to entry in nil map
fmt.Println(m1["anything"]) // 读 nil map 返回零值,安全Output
10 2
1
0comma-ok
Go
m := map[string]int{"a": 1}
// 区分「值不存在」和「值就是零」
v := m["b"]
fmt.Println(v) // 0 —— 但 b 不存在
v, ok := m["b"]
fmt.Println(v, ok) // 0 false
v, ok = m["a"]
fmt.Println(v, ok) // 1 true
// 常见写法
if v, ok := m["a"]; ok {
fmt.Println("存在,值为", v)
}Output
0
0 false
1 true
存在,值为 1这是 Go 的「双返回值」惯用法,除了 map 还用于类型断言 v, ok := x.(T) 和 channel 接收 v, ok := <-ch。看到 , ok 就知道在问「成功了吗」。
遍历
Go
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Println(k, v)
}
// 只要键
for k := range m {
_ = k
}
// 只要值
for _, v := range m {
_ = v
}
// 按键排序遍历
keys := slices.Sorted(maps.Keys(m))
for _, k := range keys {
fmt.Println(k, m[k])
}Output
a 1
b 2
c 3
a 1
b 2
c 3并发不安全
Go
m := make(map[int]int)
for i := 0; i < 100; i++ {
go func(n int) {
m[n] = n // 并发写 → fatal error: concurrent map writes
}(i)
}
time.Sleep(time.Second)Output
fatal error: concurrent map writes
goroutine 18 [running]:
main.main.func1(...)Go 的 map 没有内置锁。并发读写会触发运行时的 concurrent map read and map write 致命错误——不是 panic 能 recover 的那种,进程直接死。
三种解法
Go
// 1. 加读写锁(最常用)
var (
mu sync.RWMutex
m = map[string]int{}
)
func get(k string) int {
mu.RLock()
defer mu.RUnlock()
return m[k]
}
func set(k string, v int) {
mu.Lock()
defer mu.Unlock()
m[k] = v
}
// 2. sync.Map:读多写少、键集合稳定的场景
var sm sync.Map
sm.Store("a", 1)
v, ok := sm.Load("a")
// 3. 分片锁:极端高并发时按 key 哈希到不同锁| 方案 | 适用 |
|---|---|
sync.RWMutex + map | 绝大多数场景 |
sync.Map | 读远多于写、键基本固定(如缓存) |
| 分片锁 | 百万级 QPS 的热点 map |
sync.Map 不是「更快的 map」。它的读写要走接口,类型断言有开销,普通场景比 RWMutex + map 慢。只在「读极多、写极少」时才划算。常用操作
Go
m := map[string]int{"a": 1}
m["b"] = 2 // 增
_ = m["a"] // 查
delete(m, "b") // 删
_, exists := m["b"] // 判断存在
// 计数
counts := map[rune]int{}
for _, r := range "hello" {
counts[r]++
}
fmt.Println(counts)
// 键值反转
rev := make(map[int]string, len(counts))
for k, v := range counts {
rev[v] = string(k)
}
fmt.Println(len(rev))Output
map[101:1 104:1 108:2 111:1]
3