Go 语言 Map(集合)

声明、增删查改、comma-ok、并发安全、以及有序遍历。

Beginner
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
0

comma-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

This 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 哈希到不同锁
ApproachBest 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

Friends & Partners