重要
Go 的 map 是引用类型,函数传参时修改会影响原始 map。Map 不是并发安全的——并发读写会触发 fatal error(不可 recover)。需要并发安全时用 sync.Map 或 Mutex + map。
1. 基础操作
| |
2. Map 底层结构
Go map 使用哈希表实现,通过链地址法解决哈希冲突。每个 bucket 最多存储 8 个键值对。
| |
查找 key 时:
- 计算 hash
- 用 hash 低位定位 bucket
- 用 hash 高 8 位(tophash)快速对比
- tophash 匹配后再比较完整 key
3. 扩容机制
| 触发条件 | 类型 | 操作 |
|---|---|---|
| 负载因子 > 6.5(元素数 > bucket 数 × 6.5) | 增量扩容 | 创建 2 倍新 bucket,逐步迁移 |
| overflow bucket 过多 | 等量扩容 | 重新整理,合并碎片 |
扩容是渐进式的——每次 map 操作(读写删除)会迁移 1-2 个旧 bucket,避免一次性 STW。
4. 并发安全
Map 并发读写会 fatal error(非 panic,不可 recover):
| |
并发安全方案:
| 方案 | 适用场景 |
|---|---|
sync.Mutex + map | 写多读少 |
sync.RWMutex + map | 读多写少 |
sync.Map | 读多写少、key 集合稳定 |
sync.Map 内部原理
sync.Map 使用两个 map:read(只读,原子访问)和 dirty(写时复制)。读操作优先查 read,无锁;写操作加锁操作 dirty。当 read 中 miss 太多时,dirty 提升为 read。
| |
5. Map 作为函数参数
Map 是引用类型,传递给函数的是指针的副本,仍指向同一底层数据结构。
| |
| 类型 | 传参行为 |
|---|---|
| map | 引用传递——函数内修改影响外部 |
| slice | 引用传递——修改元素影响外部;但扩容分配新底层数组后,外部不受影响 |
| array | 值传递——完整拷贝 |
6. 总结
- Map 是引用类型,传参时修改影响原 map,但非并发安全;
- 扩容是渐进的——每次操作迁移 1-2 个 bucket,避免一次性 STW;
- 预分配容量
make(map[K]V, N)减少扩容次数; - 并发安全选型:读多写少用
sync.Map,否则用Mutex + map。