一、Map 是什么
Go 中 map 是 ** 基于哈希表实现的键值对(key-value)** 集合。
- 键唯一,元素无序
- 引用类型,必须初始化才能使用
- 增删改查时间复杂度:O(1)
- 开发高频用于:字典、缓存、去重、映射关系
基础语法格式
map[KeyType]ValueType
二、Map 四种初始化方式
1. 先声明、再初始化
var m map[string]int // 仅声明,此时为 nil
m = make(map[string]int)
2. 直接 make 初始化(最常用)
// 不指定容量
m := make(map[string]int)
// 指定初始容量(推荐,减少扩容开销)
m := make(map[string]int, 8)
3. 字面量直接初始化 + 赋值
m := map[string]int{
"张三": 18,
"李四": 20,
}
4. var 声明 + 字面量
var m = map[int]string{
1: "Go",
2: "Java",
}
核心铁律:未 make 的 nil map 不能直接存键值,会直接 panic
三、Map 基础操作:增 / 删 / 改 / 查
1. 新增 & 修改
语法统一:
- key 不存在 → 新增
- key 已存在 → 覆盖修改
m := make(map[string]int) m["数学"] = 90 // 新增 m["数学"] = 99 // 修改覆盖
2. 查找取值
双返回值(应用场景广)
val, ok := m["英语"]
if ok {
fmt.Println("键存在,值:", val)
} else {
fmt.Println("键不存在")
}
单反回值(无法区分‘0’和不存在)
val := m["英语"]
3. 删除元素
使用内置 delete 函数
// 格式:delete(map, key)
delete(m, "数学")
即使 key 不存在,delete 也不会报错
4. 获取元素个数
cnt := len(m)
四、Map 遍历 for range
map 遍历完全无序,每次遍历顺序可能不同。
1. 遍历 key + value
for k, v := range m {
fmt.Println(k, v)
}
2. 只遍历 key
for k := range m {
fmt.Println(k)
}
3. 只遍历 value
for _, v := range m {
fmt.Println(v)
}
五、Key 类型限制
合法 key(可比较类型)
int、string、bool、数组、普通结构体(无引用成员)
非法 key(不可比较)
slice、map、function、包含切片或 Map 的结构体原因:哈希表要求 key 必须可比较、能计算哈希值
六、Map 引用类型特性
map 是引用类型
- 赋值、函数传参,都是传递引用
- 函数内修改 map 数据,会直接影响原 map
示例
func modify(m map[int]int) {
m[1] = 999
}
func main() {
m := make(map[int]int)
modify(m)
}
七、Map 底层逻辑
1.底层基于哈希表 + 数组 + 链表或红黑树实现
2..核心结构
hmap:map 顶层结构体
bmap:桶 bucket,每个桶存放多个 key-value
3.哈希冲突
不同 key 哈希取模后落到同一个桶
早期链表解决冲突
新版 Go 桶内元素过多转为红黑树,提升查询效率
4.扩容机制
负载因子达到阈值,触发翻倍扩容渐进式扩容不会一次性迁移所有数据,减少卡顿
5.为什么 map 无序
扩容、桶迁移、哈希随机种子,都会导致遍历顺序打乱
八、Map 并发安全
1. 原生 map 并发不安全
- 只读不写 并发安全
- 并发写 或 读写混合 直接触发 fatal error: concurrent map writes
2. 并发解决方案
方案 1:加读写锁(RWMutex)
适合读多写少场景
import "sync"
var (
mu sync.RWMutex
m = make(map[string]int)
)
// 写操作 加写锁
mu.Lock()
m["a"] = 1
mu.Unlock()
// 读操作 加读锁
mu.RLock()
val, ok := m["a"]
mu.RUnlock()
方案 2:使用官方并发安全 Map
sync.Map
- 适合高并发、读写差异大的场景
- 无需手动加锁,开箱即用
九、完整可运行 Demo
package main
import "fmt"
func main() {
// 初始化
score := make(map[string]int, 4)
// 增改
score["语文"] = 95
score["数学"] = 100
// 查询
val, ok := score["英语"]
if !ok {
fmt.Println("英语成绩不存在")
}
// 删除
delete(score, "语文")
// 遍历
for sub, s := range score {
fmt.Printf("%s:%d\n", sub, s)
}
}
十、总结
- map 键唯一、遍历无序、引用类型
- 必须 make 初始化,nil map 禁止写入
- 取值务必用 val, ok 双返回值
- 切片、map、函数不能作为 key
- 原生 map 并发写不安全,需配合锁或 sync.Map
- 底层哈希桶结构,存在哈希冲突与渐进式扩容
1582

被折叠的 条评论
为什么被折叠?



