Go map底层结构

581 阅读2分钟

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便指向溢出桶,

整体如下图

image.png

如果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却较多呢?当然是大量的键值对被删除的情况。等量扩容后,能够减少溢出桶的使用。