← 数组 | 结构体 →

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
    })
}

📖 延伸阅读