golang 语言提供了 built-in 的 map 类型,提供 hash table 的功能。完成的特性主要有:增删查改。

定义初始化及使用注意

1
map[KeyType]ValueType

KeyType 必须是可比较的类型,可比较类型官方定义的是 boolean、numeric、string、pointer、channel、和 interface types、以及包含这些类型的 structs 或 arrays。不可比较的如 slices、maps 和 functions,用不了 ==

map 类型是引用类型,所以直接声明的变量0值是 nil,它并不指向一个初试化的 map,使用时要注意。

read nil, got zero. write nil, panic!

此外,由于 map 的 key 不存在时,默认为0值,利用这个特性也能做一些判断。

遍历 map 的顺序是随机的,如果要俺固定顺序遍历元素,可以考虑放入 slice 中。

多线程安全

map 在多线程情况下是不安全的,建议加读写锁 sync.RWMutex 一起使用。

底层实现

map = hashMap,本质是 array + list,效率极低:计算 hash 过程,查找过程

Hmap 两个 bucket 用于热扩容,不如 Java 8 的 Array + 红黑树高效,平均深度是 6.5

Hmap bucketsize 为 buckets 内 node 的数量,上限为 2 ^ B

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// A header for a Go map.
type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	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
}

// A bucket for a Go map.
type bmap struct {
	// tophash generally contains the top byte of the hash value
	// for each key in this bucket. If tophash[0] < minTopHash,
	// tophash[0] is a bucket evacuation state instead.
	tophash [bucketCnt]uint8
	// Followed by bucketCnt keys and then bucketCnt elems.
	// NOTE: packing all the keys together and then all the elems together makes the
	// code a bit more complicated than alternating key/elem/key/elem/... but it allows
	// us to eliminate padding which would be needed for, e.g., map[int64]int8.
	// Followed by an overflow pointer.
}

每个bucket中存放最多8个key/value对, 如果多于8个,那么会申请一个新的bucket,并将它与之前的bucket链起来。

将hash值的低位当作Hmap结构体中buckets数组的index,找到key所在的bucket。

将hash的高8位存储在了bucket的tophash中,先比对高 8 bit,再比对 key 是否相等。

内存布局如果是 key1/value1/key2/value2,设想如果是这样的一个 map[int64]int8,考虑到字节对齐,会浪费很多存储空间。

go-hmap

参考资料

https://go.dev/blog/maps

https://github.com/golang/go/blob/master/src/runtime/map.go