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
来实现效率要高。