Map - Go 映射类型
Map 是键值对的无序集合,是 Go 中常用的数据结构。理解 Map 的创建、操作和特性对于编写高效的 Go 代码至关重要。
Map 基础
📝 Map 定义和操作
package main
import "fmt"
func main() {
// 方式 1: make 创建
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
// 方式 2: 字面量初始化
ages := map[string]int{
"Alice": 30,
"Bob": 25,
}
// 访问元素
fmt.Println(ages["Alice"]) // 30
// 检查键是否存在
if val, ok := ages["Charlie"]; ok {
fmt.Println("Found:", val)
} else {
fmt.Println("Not found")
}
// 删除元素
delete(ages, "Bob")
// 获取长度
fmt.Println(len(ages))
// 遍历 Map
for key, value := range ages {
fmt.Printf("%s: %d\n", key, value)
}
}
💡 Map 要点
- 引用类型: Map 是引用类型
- 无序: 遍历顺序不固定
- 键类型: 必须是可比较类型
- 零值 nil: nil map 不能赋值
Map 操作
📝 常用操作
package main
import "fmt"
func main() {
// 创建并初始化
scores := map[string]int{
"Math": 95,
"English": 88,
"Science": 92,
}
// 添加/修改
scores["History"] = 85
scores["Math"] = 100
// 检查存在性
if _, exists := scores["Art"]; !exists {
fmt.Println("Art not found")
}
// 删除
delete(scores, "English")
// 只获取键
for key := range scores {
fmt.Println(key)
}
// 只获取值
for _, value := range scores {
fmt.Println(value)
}
}
高级用法
📝 Map 高级操作
package main
import "fmt"
func main() {
// 嵌套 Map
users := map[string]map[string]string{
"user1": {"name": "Alice", "email": "alice@example.com"},
"user2": {"name": "Bob", "email": "bob@example.com"},
}
// 访问嵌套 Map
fmt.Println(users["user1"]["name"])
// Map 切片
configs := []map[string]string{
{"host": "localhost", "port": "8080"},
{"host": "127.0.0.1", "port": "9000"},
}
// 切片 Map
sliceMap := map[string][]int{
"nums1": {1, 2, 3},
"nums2": {4, 5, 6},
}
fmt.Println(sliceMap["nums1"])
}
⚠️ Map 注意事项
- nil Map: 不能向 nil map 添加元素
- 并发安全: Map 不是并发安全的
- 无序遍历: 不要依赖遍历顺序
- 键类型: slice、function、map 不能做键
Map 内部实现原理
📖 Map 的底层数据结构
// runtime/map.go - Map 的底层结构
type hmap struct {
count int // 当前元素个数
flags uint8 // 状态标志
B uint8 // bucket 数量的对数 (2^B 个 bucket)
noverflow uint16 // 溢出 bucket 的数量
hash0 uint32 // 哈希种子
buckets unsafe.Pointer // bucket 数组指针
oldbuckets unsafe.Pointer // 旧 bucket 数组 (扩容时使用)
nevacuate uintptr // 已迁移的 bucket 数量
// ... 更多字段
}
// bucket 结构
type bmap struct {
tophash [8]uint8 // 哈希值的高 8 位
// 键值对数据...
// overflow 指针...
}
hmap
→
buckets 数组
→
bucket[0]
tophash[8]
|
keys[8]
|
values[8]
📝 Map 结构图示
// Map 内存布局:
// hmap
// ├── count: 5
// ├── B: 3 (8 个 bucket)
// ├── buckets → [bucket0] [bucket1] ... [bucket7]
// └── hash0: 随机种子
// 每个 bucket 存储 8 个键值对:
// bucket:
// ├── tophash[8]: 哈希高 8 位 (快速查找)
// ├── keys[8]: 键数据
// ├── values[8]: 值数据
// └── overflow: 指向下一个 bucket (冲突时)
// 查找流程:
// 1. 计算 key 的哈希值
// 2. 用哈希低 B 位确定 bucket 索引
// 3. 用哈希高 8 位匹配 tophash
// 4. 完全比较 key 确认
// 5. 返回对应的 value
哈希冲突处理
📖 链地址法
// Go Map 使用链地址法解决哈希冲突
// 每个 bucket 最多存储 8 个键值对
// 超过 8 个时,创建 overflow bucket
// 示例:
m := make(map[int]string)
// 假设 key 1, 9, 17 哈希冲突 (都映射到 bucket 0)
m[1] = "one"
m[9] = "nine"
m[17] = "seventeen"
// 内存布局:
// bucket[0] → [1, 9, 17, ...] (前 8 个)
// ↓ overflow
// overflow_bucket → [...](超出 8 个的部分)
扩容机制
📖 渐进式扩容
// 扩容触发条件:
// 1. 负载因子 > 6.5 (count / 2^B > 6.5)
// 2. overflow bucket 过多
// 扩容流程 (渐进式):
// 1. 分配新的 bucket 数组 (容量翻倍)
// 2. 设置 oldbuckets 指向旧数组
// 3. 每次插入/删除时迁移 2 个 bucket
// 4. 所有 bucket 迁移完成后,释放旧数组
// 示例:
m := make(map[string]int, 100)
// 初始:B=3, 8 个 bucket, 负载因子 12.5
// 触发扩容 → B=4, 16 个 bucket
// 扩容期间:
// - hmap.buckets 指向新数组
// - hmap.oldbuckets 指向旧数组
// - hmap.nevacuate 记录已迁移的 bucket 数
// 扩容完成:
// - oldbuckets = nil
// - 旧数组被 GC 回收
💡 Map 性能优化
- 预分配容量: 使用
make(map[K]V, hint)减少扩容 - 避免频繁删除: 删除不会缩小 map,只标记删除
- 注意内存: 大 map 删除元素后内存不会释放
- 并发访问: 使用
sync.Map或加锁保护 - 键的选择: 使用简单类型作为键,避免复杂结构
并发 Map
📝 sync.Map 并发安全
package main
import (
"fmt"
"sync"
)
func main() {
var m sync.Map
// 存储
m.Store("key1", "value1")
// 读取
val, ok := m.Load("key1")
fmt.Println(val, ok)
// 读取或存储
val, loaded := m.LoadOrStore("key2", "value2")
// 删除
m.Delete("key1")
// 遍历
m.Range(func(key, value interface{}) bool {
fmt.Printf("%v: %v\n", key, value)
return true
})
}