map 实现与 slice 的不同

slice 实际是个值类型,其中包含了一个指向底层数组的指针,而 map 可以理解为引用类型, make(map[Type][Type]) 创建的 map 实际包含一个指向实际 hmap 结构的指针,所以在函数调用中对 map 的修改都会对外层可见。

同时 map 并不支持零值可用,对没有显式赋值的 map 操作会导致 panic。

map 的 key 和 value 类型

map 对 value 的类型没有要求,但对 key 的类型有要求,key 的类型应该是 comparable 的,其类型严格定义了 ==!= 操作符行为。所以 function, map, slice 不能作为 map 的 key。

另外,放入 map 中的 key 和 value 都不可取地址,即不可以用 & 操作符获得其指针。

map 遍历的顺序是不确定的

这是故意设计的,程序不应当依赖 hashmap 的顺序。

map 删除 key 时不会自动缩容

同 slice 一样 map 也不会自动缩容,如果程序中有生命周期长的 map 也应当考虑手动缩容的操作。

map 的内部实现

map 的实现在 runtime/map.go 中, 主要的类型为 hmap

// A header for a Go map.
type hmap struct {
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed
 
	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
 
	extra *mapextra // optional fields
}

具体的实现可以参照下面这张图:

Tips

go 1.24 之后,golang 的 map 采用了新的基于 SwissTable 的实现,显著提升了大型 map 的访问速度

concurrent map

本身 map 并不支持并发访问,如果需要并发访问,可以使用 sync.Map,其内部是通过 atomic 的原语实现,是无锁实现,会比自己使用 sync.Mutex 来实现效率要高。