Map 类型 - Map Types

← 返回数据类型

Map 是 Go 语言的哈希表,提供键值对存储。

Map 基础

创建 Map

// 使用 make 创建
m1 := make(map[string]int)

// 使用字面量创建
m2 := map[string]int{
    "apple":  5,
    "banana": 3,
}
fmt.Println(m2)  // map[apple:5 banana:3]
必须初始化
map 必须初始化才能使用
// ❌ 错误:仅定义
var m map[string]int
m["key"] = 1  // panic: assignment to entry in nil map

// ✅ 正确:使用 make 初始化
m := make(map[string]int)
m["key"] = 1

Map 操作

m := make(map[string]int)

// 添加/修改元素
m["key1"] = 10
m["key2"] = 20

// 读取元素
v := m["key1"]
fmt.Println(v)  // 10

// 读取带检查(推荐)
v, ok := m["key1"]
if ok {
    fmt.Println("存在:", v)
} else {
    fmt.Println("不存在")
}

// 删除元素
delete(m, "key1")

// 获取长度
fmt.Println(len(m))  // 1

Map 遍历

m := map[string]int{
    "apple":  5,
    "banana": 3,
    "cherry": 7,
}

// 遍历 map(顺序不确定)
for key, value := range m {
    fmt.Printf("%s: %d\n", key, value)
}

// 只遍历键
for key := range m {
    fmt.Println(key)
}

// 只遍历值
for _, value := range m {
    fmt.Println(value)
}

注意 Map 的遍历顺序是随机的,每次运行可能不同。

Map 特性

键类型要求

// 可比较的类型都能作为键
m1 := map[int]string{1: "one"}
m2 := map[string]int{"one": 1}
m3 := map[bool]string{true: "yes"}

// ❌ 不能使用 slice、map、func 作为键
// m4 := map[[]int]string{}  // 编译错误

// ✅ 可以使用数组作为键(长度固定)
m5 := map[[3]int]string{[3]int{1, 2, 3}: "array key"}

零值和删除

m := make(map[string]int)

// 读取不存在的键返回零值
v := m["notexist"]
fmt.Println(v)  // 0

// 删除不存在的键不会报错
delete(m, "notexist")  // 什么都不会发生

// 检查键是否存在
if v, ok := m["key"]; ok {
    fmt.Println("存在:", v)
}

Map 实现原理

哈希冲突

Go 使用链地址法解决哈希冲突:

扩容机制

  • 负载因子 > 6.5 时触发扩容
  • 扩容时桶数量翻倍
  • 渐进式 rehash,避免一次性迁移

并发安全

// ❌ 并发写入会 panic
m := make(map[int]int)
go func() {
    for i := 0; i < 1000; i++ {
        m[i] = i
    }
}()
go func() {
    for i := 0; i < 1000; i++ {
        m[i] = i * 2
    }
}()
// panic: concurrent map writes

解决方案 使用 sync.Mutexsync.RWMutex 保护 map:

import "sync"

type SafeMap struct {
    mu   sync.RWMutex
    data map[string]int
}

func (sm *SafeMap) Get(key string) (int, bool) {
    sm.mu.RLock()
    defer sm.mu.RUnlock()
    v, ok := sm.data[key]
    return v, ok
}

func (sm *SafeMap) Set(key string, value int) {
    sm.mu.Lock()
    defer sm.mu.Unlock()
    sm.data[key] = value
}

或使用 Go 1.9+ 的 sync.Map

var m sync.Map

m.Store("key", "value")
if v, ok := m.Load("key"); ok {
    fmt.Println(v)
}
m.Delete("key")

Map 使用技巧

统计频率

s := "hello world"

// 统计字符出现次数
freq := make(map[rune]int)
for _, c := range s {
    freq[c]++
}

fmt.Println(freq)  // map[ :1 h:1 e:1 l:3 o:2 w:1 r:1 d:1]

分组

type Person struct {
    Name string
    Age  int
}

people := []Person{
    {"Alice", 25},
    {"Bob", 30},
    {"Charlie", 25},
}

// 按年龄分组
groups := make(map[int][]Person)
for _, p := range people {
    groups[p.Age] = append(groups[p.Age], p)
}

fmt.Println(groups)
// map[25:[{Alice 25} {Charlie 25}] 30:[{Bob 30}]]

Map vs Slice

特性MapSlice
索引任意键(可比较类型)整数索引
查找O(1) 平均O(n) 线性
有序性无序有序
使用场景键值对、快速查找有序集合、序列

练习

  1. 使用 map 统计文本中每个单词出现的次数
  2. 实现两个 map 的合并功能
  3. 创建 map 存储 slice,实现多值映射

← 切片 | 结构体 →