Go采用的是哈希查找表,使用链表解决哈希冲突来实现map
源码中map的结构体是hmap:
// A header for a Go map.
type hmap struct {
// 元素个数,调用 len(map) 时,直接返回此值
count int
flags uint8
// buckets 的对数 log_2
B uint8
// overflow 的 bucket 近似数
noverflow uint16
// 计算 key 的哈希的时候会传入哈希函数
hash0 uint32
// 指向 buckets 数组,大小为 2^B
// 如果元素个数为0,就为 nil
buckets unsafe.Pointer
// 扩容的时候,buckets 长度会是 oldbuckets 的两倍
oldbuckets unsafe.Pointer
// 指示扩容进度,小于此地址的 buckets 迁移完成
nevacuate uintptr
extra *mapextra // optional fields
}
//buckets指向的结构体
type bmap struct {
tophash [bucketCnt]uint8
}
在编译期间,bmap的结构会发生变化
type bmap struct {
topbits [8]uint8
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
bmap实际上就是bucket的内存结构,每个bucket中最多存8个key,实际上是时间和空间的权衡(如果每个只存一个,那么会占用很大的空间,如果每个可以存很多个,极端情况下查改时间复杂度变成O(n))
图片转载自https://www.cnblogs.com/qcrao-2018/p/10903807.html
当map的key,value都不是指针并且size都小鱼128字节时会把bmap标记为不含指针,这时gc时就可以不用遍历整个hmap。bmap中其实有一个overflow的指针字段,这会使得bmap不满足不含指针,此时会把overflow移动到extra字段中
type mapextra struct {
// overflow[0] contains overflow buckets for hmap.buckets.
// overflow[1] contains overflow buckets for hmap.oldbuckets.
overflow [2]*[]*bmap
// nextOverflow 包含空闲的 overflow bucket,这是预分配的 bucket
nextOverflow *bmap
}
bmap的内部组成
图片转自https://www.cnblogs.com/qcrao-2018/p/10903807.html
可以看到每个bmap中除了存储了key,value之外还有一个overflow指针。此外,可以看到key和value是各自放在一起的而不是key|value|key|value这样的,源码中说明这样的好处是某些情况下可以省略掉padding,节省内存空间。padding解释参考 在 Go 中恰到好处的内存对齐
。
一个bucket只能存放8个键值对,如果有更多的键值对被添加到当前的bucket,就会重新创建一个新的bucket,用overflow指针来连接。