1. hmap
Go 中 map 的底层结构是hmap:
type hmap struct {
count int //元素个数,调用len(map)时直接返回
flags uint8 //标志map当前状态,正在删除元素、添加元素.....
B uint8 //记录桶的次幂
noverflow uint16 //记录使用的溢出桶的数量
hash0 uint32 //哈希种子
buckets unsafe.Pointer //当前通,指向单元(buckets)数组,大小为2^B
oldbuckets unsafe.Pointer //扩容的时候,记录旧桶在哪,buckets长度会是oldbuckets的两倍
nevacute uintptr //渐进式扩容时,即将迁移的旧桶编号
extra *mapextra //mapextra结构体,用于记录溢出桶的信息
}
2. bmap
来看一看map中的bucket的结构,为bmap:
//a bucket for a Go map
type bmap struct {
tophash [bucketCnt]uint8 //8位,占一字节
}
type bmap struct {
topbits [8]uint8
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
bmp也就是bucket,里面最多存8个key,每个key落在桶的位置由hash出来的结果的高8位决定。
当一个bucket满了后,后续的元素会往溢出桶里存,溢出桶也是bmap结构,overflow便指向溢出桶,
整体如下图
如果hmap要分配的桶的数目大于 2^4,就认为使用到溢出桶的几率比较大,就会预分配 2^(B-4)个溢出桶备用。这些溢出桶与常规桶在内存中是连续的。
3. 扩容规则
hmap扩容的负载因子是6.5,即count/(2^B)>6.5时就会发生扩容。分配新桶的数目是旧桶的两倍,hmap.B++。
在负载因子没有超标,noverflow较多的情况下,会发生等量扩容。什么情况下noverflow算多呢?如果 B<=15,则noverflow >= 2B算多,发生扩容。如果 B>15,则noverflow >= 215算多,发生扩容。
什么是等量扩容,即创建和旧桶数目一样多的新桶,把旧桶中的数据放进去。但这样做的意义在哪呢?
首先我们要理解,为什么负载因子没有超标,但noverflow却较多呢?当然是大量的键值对被删除的情况。等量扩容后,能够减少溢出桶的使用。