在 Go 语言中,我们通常会遇到两种声明 map 的方式:声明一个未初始化的 map(即 nil map)和通过 make 关键字初始化一个空 map。
var m1 map[int]int // nil map
m2 := make(map[int]int) // 空 map虽然它们在某些读取操作上表现一致,但它们在底层的内存分配和写入行为上存在着本质的区别。
nil map
通过 var 关键字声明但未初始化的 map 被称为 nil map。在底层,Go 仅仅在内存中分配了一个 map 类型的指针,并没有为其分配底层的 hmap 结构体内存,此时该指针的值为 nil。
- 读取安全:对 nil map 进行键值读取(如
v := m1[1])是安全的,不会引发 panic,而是直接返回 value 类型的零值(如0)。 - 长度为零:执行
len(m1)操作是合法的,返回值始终为0。 - 严禁写入:尝试向 nil map 写入数据(如
m1[1] = 1)会导致运行时 panic(panic: assignment to entry in nil map),因为底层根本不存在用于存储数据的hmap结构和哈希桶。
空 map
通过 make 函数或字面量(如 map[int]int{})初始化的 map 被称为空 map。
在底层,编译器会将其转换为对 runtime.makemap(或针对小 map 优化的 makemap_small)函数的调用。这会在堆或栈上分配一个完整的 hmap 结构体,并初始化其必要的内部字段(例如用于哈希计算的随机种子 hash0)。
- 完全可用:这是一个已完全初始化的 map,可以正常进行任何读写及删除操作。
- 延迟分配桶:虽然
hmap结构体已分配,但为了优化内存,此时它可能还没有分配任何实际的哈希桶(即B=0)。底层的桶数组通常会在第一次发生写入操作时进行延迟分配。
总结:如果仅仅是为了读取数据或作为参数传递,nil map 是安全的;但如果需要向 map 中写入数据,则必须使用 make 或字面量对其进行初始化。