一、简介
Go 语言内置 键值对 数据结构,它有如下特点
- 无序,键值对的顺序是随机的
- 键唯一,不可重复
- 键必须是可比较类型
- 值可以是任意类型
- 查找、插入、删除的平均时间复杂度都是 O(1)
二、创建映射
使用字面量创建
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
使用 make 创建空 map
ages := make(map[string]int)
ages["Tom"] = 20
使用 var 声明
var ages map[string]int
fmt.Println(ages == nil) // true
// ages["Tom"] = 20 // panic:不能往 nil map 写入
注意:只能对非 nil map 进行赋值
读取 nil map 是安全的
三、基本操作
插入或更新
ages["Alice"] = 25
访问键值
fmt.Println(ages["Alice"]) // 25
判断键是否存在
age, ok := ages["Bob"]
if ok {
fmt.Println("存在:", age)
} else {
fmt.Println("不存在")
}
删除键
delete(ages, "Alice")
遍历 map
for key, value := range ages {
fmt.Println(key, value)
}
四、零值行为
var m map[string]int
fmt.Println(m == nil) // true
fmt.Println(m["nothing"]) // 0(零值,不会 panic)
但不能往 nil map 写入值
五、值类型
值可以是任意类型,包括:
- 基本类型
- 结构体
- 切片
- map(嵌套 map)
嵌套 map 示例:
students := map[string]map[string]int{
"class1": {"Alice": 90, "Bob": 80},
"class2": {"Tom": 95},
}
fmt.Println(students["class1"]["Alice"])
六、容量与扩容机制
虽然 make(map[T]U, n) 可以指定初始容量,但:
它只是提示容量,不是限制,也不是固定大小
当存入更多键值时,map 会自动扩容,底层重新分配桶
七、常见技巧与最佳实践
最佳创建方式
m := make(map[string]int)
m["A"] = 1
- 错误写法
var m map[string]int
m["A"] = 1 // panic: assignment to entry in nil map
解释:
var 声明的 map 是 nil,必须先用 make 分配空间才能写入
预估容量提升性能
m := make(map[string]int, 1000)
Go 的第二个参数只是容量提示,不是固定大小但给出合理容量可以减少哈希桶扩容次数,显著提升性能
用字面量初始化更简洁
m := map[string]int{
"apple": 5,
"banana": 7,
}
判断键是否存在的正确姿势
if v, ok := m["apple"]; ok {
fmt.Println("存在:", v)
} else {
fmt.Println("不存在")
}
注意:直接判断
if m["apple"] == 0不安全,因为零值和不存在是一样的
删除键时使用 delete()
delete(m, "apple")
删除不存在的键不会报错,相当于安全操作
遍历时不要依赖顺序
for k, v := range m {
fmt.Println(k, v)
}
Go 的 map 是无序的。如果需要排序输出
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}
不能直接取元素地址
// 编译错误
p := &m["key"] // cannot take the address of m["key"]
因为 map 扩容时键值位置会改变,地址不稳定
修改值时应先取出再写回
v := m["count"]
v++
m["count"] = v
避免在循环中频繁创建 map
// 不推荐
for i := 0; i < 1000; i++ {
m := make(map[string]int)
m["x"] = i
}
应该复用一个 map 或放循环外部
大量 map 查找性能建议
- 尽量使用整型或短字符串作为 key;
- 避免使用复杂结构体作为 key;
- 如果 key 是 struct,确保字段可比较(不要包含 slice/map)
若要保留内存空间,可逐个删除
for k := range m {
delete(m, k)
}