2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, go基础语法, map, 哈希表]
|
|
|
|
|
|
create time: 2026-06-07 15:00
|
2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
# Go 语言 Map
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 概述
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Map 是 Go 的内置哈希表,提供 O(1) 平均复杂度的键值对操作。本文覆盖创建、增删改查、遍历时序随机性、并发安全等核心要点。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 正文
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 为什么 Go 的 Map 遍历顺序是随机的?
|
|
|
|
|
|
|
|
|
|
|
|
这不是 bug,而是**有意设计**。Go 的 map 在每次遍历时会随机打乱起始位置,目的是**防止程序员依赖遍历顺序**——而依赖未定义行为正是很多 bug 的源头。
|
|
|
|
|
|
|
|
|
|
|
|
> [!warning] ⚠️ 记住这条规则
|
|
|
|
|
|
> **永远不要依赖 map 的遍历顺序。** 如果需要有序输出,先收集 key 再排序。
|
|
|
|
|
|
|
|
|
|
|
|
### 创建与初始化
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 方式一:make(空 map)
|
|
|
|
|
|
m := make(map[string]int)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 方式二:字面量(带初始值)
|
|
|
|
|
|
scores := map[string]int{
|
2026-06-07 11:08:10 +08:00
|
|
|
|
"张三": 95,
|
|
|
|
|
|
"李四": 88,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 方式三:var 声明(零值为 nil,需 make 后才能用)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
var prices map[string]float64
|
|
|
|
|
|
prices = make(map[string]float64)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 nil map 不能写入
|
|
|
|
|
|
> `var m map[string]int` 创建的 map 是 `nil`。对它做 `m["a"] = 1` 会 panic。必须先 `make`。
|
|
|
|
|
|
|
|
|
|
|
|
### 基本操作
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 写入 / 更新
|
|
|
|
|
|
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("不存在")
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 删除
|
|
|
|
|
|
delete(m, "apple")
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 获取元素个数
|
|
|
|
|
|
fmt.Println(len(m))
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!tip] 💡 单返回值惯用法
|
|
|
|
|
|
> 判断 key 是否存在还有一种更简洁的写法:
|
|
|
|
|
|
> ```go
|
|
|
|
|
|
> if price, ok := m["apple"]; ok {
|
|
|
|
|
|
> // key 存在
|
|
|
|
|
|
> }
|
|
|
|
|
|
> ```
|
|
|
|
|
|
> `ok` 是 Go 中约定俗成的布尔变量名,表示"是否成功"。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 嵌套 Map
|
|
|
|
|
|
|
|
|
|
|
|
Map 的值可以是另一个 Map:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
studentScores := map[string]map[string]int{
|
|
|
|
|
|
"张三": {"数学": 95, "英语": 88},
|
|
|
|
|
|
"李四": {"数学": 90, "英语": 85},
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取张三的英语成绩
|
|
|
|
|
|
score := studentScores["张三"]["英语"]
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ 需要先初始化内层 map
|
|
|
|
|
|
> ```go
|
|
|
|
|
|
> studentScores["王五"] = make(map[string]int) // 必须先 make
|
|
|
|
|
|
> studentScores["王五"]["数学"] = 92 // 然后才能写入
|
|
|
|
|
|
> ```
|
|
|
|
|
|
|
|
|
|
|
|
### Map 作为函数参数
|
|
|
|
|
|
|
|
|
|
|
|
Map 是**引用类型**——传入函数后修改会影响原始 map:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func addScore(scores map[string]int, name string, score int) {
|
|
|
|
|
|
scores[name] = score // 直接修改原 map
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
m := map[string]int{"张三": 95}
|
|
|
|
|
|
addScore(m, "李四", 88)
|
|
|
|
|
|
fmt.Println(m) // map[张三:95 李四:88]
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 性能优化:预分配容量
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
如果知道 map 的大致大小,传 `cap` 可以减少扩容开销:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 预计约 1000 个元素
|
|
|
|
|
|
m := make(map[string]int, 1000)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!info] ℹ️ 注意
|
|
|
|
|
|
> `make(map[T]U, 1000)` 中的第二个参数是**容量提示**(不是强制限制)。map 仍会自动扩容,但预分配可以减少重新分配的次数。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 并发安全
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
**普通 map 不是并发安全的**。多个 goroutine 同时写会 panic:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-06-07 12:14:39 +08:00
|
|
|
|
fatal error: concurrent map writes
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 方案一:sync.Mutex
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var mu sync.Mutex
|
|
|
|
|
|
m := make(map[string]int)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
mu.Lock()
|
|
|
|
|
|
m["key"] = 1
|
|
|
|
|
|
mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
mu.Lock()
|
|
|
|
|
|
val := m["key"]
|
|
|
|
|
|
mu.Unlock()
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### 方案二:sync.Map
|
|
|
|
|
|
|
|
|
|
|
|
适用于读多写少或 key 集合固定的场景:
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var sm sync.Map
|
|
|
|
|
|
|
|
|
|
|
|
sm.Store("key", 42) // 写入
|
|
|
|
|
|
val, ok := sm.Load("key") // 读取 → (interface{}, bool)
|
|
|
|
|
|
sm.Delete("key") // 删除
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 sync.Map vs Mutex+map
|
|
|
|
|
|
> - `sync.Map` 适合 key 集固定、读远多于写的场景
|
|
|
|
|
|
> - 写频繁时,`sync.Mutex + map` 通常性能更好
|
|
|
|
|
|
> - 具体选择取决于你的读写比例
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 常见陷阱总结
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 陷阱 | 说明 | 修复 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| nil map 写入 | `var m map[K]V` 后直接赋值会 panic | 先用 `make` |
|
|
|
|
|
|
| 不存在的 key 返回零值 | `m["missing"]` 返回类型的零值 | 用 `v, ok := m[k]` 判断 |
|
|
|
|
|
|
| 遍历顺序随机 | 每次运行顺序不同 | 需要有序则先排序 key |
|
|
|
|
|
|
| 并发写 panic | 多 goroutine 同时写 | 加锁或用 `sync.Map` |
|
|
|
|
|
|
| 不能取元素地址 | `&m["key"]` 编译错误 | 无法直接取 map 值的地址 |
|