目录

Go 语言 Map

Map(映射)是 Go 语言中重要的引用类型,用于存储无序的键值对集合。Map 通过 key 快速查找 value,查找时间复杂度为 O(1)。在日常开发中,map 被广泛用于数据索引、计数统计、对象缓存等场景。

Map 的创建

使用 make 创建

package main

import "fmt"

func main() {
    // 创建一个空的 map,key 为 string,value 为 int
    m1 := make(map[string]int)
    fmt.Println(m1)       // map[]
    fmt.Printf("类型: %T\n", m1) // map[string]int

    // 创建时指定初始容量
    m2 := make(map[string]int, 100)
    _ = m2
}

make(map[K]V, hint) 的第二个参数是容量提示,预分配足够的桶(bucket)数量以减少扩容。

使用字面量创建

package main

import "fmt"

func main() {
    // 字面量初始化
    m := map[string]int{
        "Go":     1,
        "Java":   2,
        "Python": 3,
    }
    fmt.Println(m) // map[Go:1 Java:2 Python:3]
    fmt.Printf("长度: %d\n", len(m)) // 3
}

注意:声明但未初始化的 map 值为 nil,对 nil map 读取不会 panic(返回零值),但写入会 panic。使用前必须用 make 或字面量初始化。

增删改查操作

package main

import "fmt"

func main() {
    m := make(map[string]int)

    // 增:添加键值对
    m["Go"] = 1
    m["Java"] = 2
    m["Python"] = 3
    fmt.Println(m) // map[Go:1 Java:2 Python:3]

    // 查:通过 key 获取 value
    fmt.Println(m["Go"]) // 1

    // 改:修改已存在的 key 对应的值
    m["Go"] = 10
    fmt.Println(m["Go"]) // 10

    // 删:使用 delete 函数删除键值对
    delete(m, "Java")
    fmt.Println(m) // map[Go:10 Python:3]
}

判断 key 是否存在(comma ok 模式)

直接通过 key 访问 map 时,如果 key 不存在,会返回 value 类型的零值。但有时我们需要区分"key 不存在"和"value 恰好是零值"的情况。

package main

import "fmt"

func main() {
    m := map[string]int{
        "Go":     1,
        "Python": 0, // value 为 0
    }

    // comma ok 模式
    val, ok := m["Go"]
    if ok {
        fmt.Printf("Go 存在,值为 %d\n", val) // Go 存在,值为 1
    }

    val, ok = m["Python"]
    if ok {
        fmt.Printf("Python 存在,值为 %d\n", val) // Python 存在,值为 0
    }

    val, ok = m["Java"]
    if !ok {
        fmt.Println("Java 不存在") // Java 不存在
    } else {
        fmt.Printf("Java 的值为 %d\n", val)
    }

    // 只判断是否存在,不关心值
    if _, ok := m["Rust"]; !ok {
        fmt.Println("Rust 不在 map 中")
    }
}

Map 遍历

Map 的遍历使用 for-range,但遍历顺序是随机的,每次运行结果可能不同。

package main

import "fmt"

func main() {
    m := map[string]int{
        "Go":     1,
        "Java":   2,
        "Python": 3,
        "Rust":   4,
    }

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

如果需要有序遍历,可以先提取 key 并排序:

package main

import (
    "fmt"
    "sort"
)

func main() {
    m := map[string]int{
        "Go":     1,
        "Java":   2,
        "Python": 3,
        "Rust":   4,
    }

    // 提取所有 key
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }

    // 排序 key
    sort.Strings(keys)

    // 按排序后的顺序遍历
    for _, k := range keys {
        fmt.Printf("%s: %d\n", k, m[k])
    }
}

Map 的 key 类型限制

Map 的 key 必须是可比较的类型(即支持 == 运算符)。

合法的 key 类型:

  • 基本类型:stringintfloat64bool
  • 指针类型
  • 结构体(所有字段都可比较时)
  • 数组(元素类型可比较时)

非法的 key 类型:

  • 切片(slice)
  • Map
  • 函数(func)
package main

import "fmt"

func main() {
    // 合法:使用结构体作为 key
    type Point struct {
        X, Y int
    }
    m := map[Point]string{
        {1, 2}: "A",
        {3, 4}: "B",
    }
    fmt.Println(m[Point{1, 2}]) // A

    // 非法:使用切片作为 key(编译错误)
    // m2 := map[[]string]int{} // invalid map key type
}

Map 是引用类型

Map 是引用类型,赋值和传参时不会拷贝底层数据。

package main

import "fmt"

func addEntry(m map[string]int) {
    m["new"] = 100 // 修改会影响原始 map
}

func main() {
    m1 := map[string]int{"a": 1, "b": 2}
    m2 := m1 // m1 和 m2 指向同一个底层数据结构

    m2["c"] = 3
    fmt.Println(m1) // map[a:1 b:2 c:3] —— m1 也被修改了

    addEntry(m1)
    fmt.Println(m1) // map[a:1 b:2 c:3 new:100]
}

如果需要独立的副本,需要手动遍历复制:

m2 := make(map[string]int, len(m1))
for k, v := range m1 {
    m2[k] = v
}

https://img.zhaojq.top/20260729161537314.png
Map哈希结构

删除元素

使用内置函数 delete(m, key) 删除 map 中的键值对。

package main

import "fmt"

func main() {
    m := map[string]int{
        "Go":     1,
        "Java":   2,
        "Python": 3,
    }

    // 删除指定的 key
    delete(m, "Java")
    fmt.Println(m) // map[Go:1 Python:3]

    // 删除不存在的 key,不会 panic,什么也不做
    delete(m, "Rust")
    fmt.Println(m) // map[Go:1 Python:3]

    // 删除 nil map 会 panic
    // var m2 map[string]int
    // delete(m2, "key") // 不会 panic,对 nil map 的 delete 是安全的
}

注意:delete 对 nil map 操作是安全的,不会 panic。

并发访问 Map 的问题

Go 的 map 不是并发程安全的。多个 goroutine 同时读写 map 会导致 fatal error: concurrent map writes

package main

import (
    "fmt"
    "sync"
)

func main() {
    m := make(map[int]int)
    var wg sync.WaitGroup

    // 这段代码运行时可能会 panic
    for i := 0; i < 100; i++ {
        wg.Add(1)
        go func(n int) {
            defer wg.Done()
            m[n] = n * n // 并发写入,不安全!
        }(i)
    }

    wg.Wait()
    fmt.Println(len(m))
}

运行上述代码,大概率会看到 fatal error: concurrent map writes

解决方案:使用 sync.Mutex

package main

import (
    "fmt"
    "sync"
)

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

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

func (s *SafeMap) Get(key int) (int, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    val, ok := s.m[key]
    return val, ok
}

func main() {
    sm := &SafeMap{m: make(map[int]int)}
    sm.Set(1, 100)
    sm.Set(2, 200)

    val, ok := sm.Get(1)
    fmt.Printf("key=1: val=%d, exists=%v\n", val, ok) // key=1: val=100, exists=true
}

sync.Map 简介

Go 1.9 引入了 sync.Map,它是并发安全的 map 实现,适用于读多写少、多个 goroutine 读写不相交 key 的场景。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var sm sync.Map

    // 存储
    sm.Store("Go", 1)
    sm.Store("Java", 2)

    // 读取
    val, ok := sm.Load("Go")
    if ok {
        fmt.Printf("Go: %v\n", val) // Go: 1
    }

    // LoadOrStore:如果 key 存在则返回已有值,否则存储新值
    actual, loaded := sm.LoadOrStore("Go", 100)
    fmt.Printf("值: %v, 已存在: %v\n", actual, loaded) // 值: 1, 已存在: true

    // 删除
    sm.Delete("Java")

    // 遍历
    sm.Range(func(key, value interface{}) bool {
        fmt.Printf("%v: %v\n", key, value)
        return true
    })
}

sync.Map 与内置 map 的选择:

  • 一般场景优先使用内置 map + sync.RWMutex,更灵活
  • 读多写少的并发场景使用 sync.Map,性能更好

总结

本篇介绍了 Go 语言 Map 的核心知识:map 的创建方式(make、字面量)、增删改查操作、comma ok 模式判断 key 是否存在、map 的无序遍历特性及有序遍历方法、key 类型限制、引用类型特性、delete 函数、并发访问的安全问题以及 sync.Map 的使用场景。Map 是 Go 中最常用的数据结构之一,理解其特性和限制对于编写正确、高效的 Go 程序至关重要。