2021-03-29 go语言map理解

  1. Go中的map在底层是用哈希表实现的,你可以在 $GOROOT/src/pkg/runtime/hashmap.goc 找到它的实现。

  2. struct Hmap
    {
        uint8   B;    // 可以容纳2^B个项
        uint16  bucketsize;   // 每个桶的大小
        byte    *buckets;     // 2^B个Buckets的数组
        byte    *oldbuckets;  // 前一个buckets,只有当正在扩容时才不为空
    };

上面是哈希表的数据结构的一部分域,结构体中有一个buckets和一个oldbuckets是用来实现增量扩容的。正常情况下直接使用buckets,而oldbuckets为空。如果当前哈希表正在扩容中,则oldbuckets不为空,并且buckets大小是oldbuckets大小的两倍。

这个hash结构使用的是可扩展的哈希算法,由hash值mod当前hash表大小决定某个值是属于哪个桶的,而哈希表的大小是2的指数,每次扩容都是上次大小的两倍。

struct Bucket
{
    uint8  tophash[BUCKETSIZE]; // hash值的高8位....低位从bucket的array定位到bucket
    Bucket *overflow;           // 溢出桶链表,如果有
    byte   data[1];             // BUCKETSIZE keys followed by BUCKETSIZE values
};

 上图是桶的结构,tophash是hash值的高八位,每个桶最多存放八对key/vlue值,如果超出,需要申请新的桶,并将新的桶和之前的bucket链接起来

按照key的类型用相应的hash算法得到key的hash值,将hash值的低位当做是buckets数据的下标,找到对应的key所在的bucket,将hash值的高八位存储在buckets的tophash中,注意:这里的高八位是作为主键存放在tophash中的,在查找时先对tophash数组的每一项进行顺序匹配,在比较hash值的高八位和tophash的某一位相等,然后再比较对应位置的key与所给的key值是否相等,如果相等,返回对应的value,否则在overflow buckets中按照上面方法查找。

在buckets中key和value是分开存放的,key放在一起,values放在一起,因为考虑到字节对齐,会浪费很多内存空间

增量扩容

  1. 如果扩容前的哈希表的大小为2^B,扩容以后的大小为2^(B+1),每次扩容为原来的两倍,如果哈希表的大小始终为2的指数倍
  2. 扩容之后由于算法的问题,需要重新计算没一项hash表的新位置,当扩容后,需要将旧的pair放到新的table上,这个工作不是一次性完成的,是逐步完成的。这就是增量扩容
  3. 为啥使用增量扩容了,比如我们直接将一个map用于实时性要求比较高的服务应用中,如果不是增量扩容,当map中保存数据多之后,扩容就容易卡住,导致长时间的无法响应。
  4. 在扩容时,将旧的bucket搬到新的表中之后,并不会讲旧的butcket从oldbucket中删除,而是加了一个标记删除
  5. 由于扩容工作是逐步完成的,所以就导致有一部分数据是在oldtable中,一部分在newtable中,所以对map的一系列查询,插入都有影响,只有当所有的bucket都从旧表移动到新表之后lodbucket才会释放。

查询过程

  1.  根据key的类型用对应的hash算法计算hash值。
  2. 查看时候有oldbuckets,存在则先在oldbuckets中查询,依据hash值的第八位定位是那个bucket(第八位用于定位bucket的下标),如果找到的bucket已经被标记删除了,说明已经移动到了新的tables中,则到新的table中查找,依据hash的高八位来查询对应的值在bucket中的位置。
  3. 不存在oldbucket,那么就依据hash值的第八位定位bucket的位置,然后依据高八位定位在bucket中的位置,返回value
  4. 高八位不是bucket内部的offset,它的作用是加快key值的比较,原因是因为key的类型复杂,太多的比较会影响速度,用hash比较更快。

插入过程

  1. 根据key值的类型用对应的hash算法计算hash值。得出对应的bucket
  2. 如果bucket在oldbucket中,先将改bucket移动到newtable中
  3. 在bucket中查询空位,如果有就直接插入赋值,如果已经存在key值,更新
  4. 如果bucket已经满了,重新申请一个bucket作为溢出bucket
  5. 将数据插入到新的bucket中

      在插入过程中,oldbuckets是被冻结的,不会在oldbuckets中插入数据的,是将bucket移动到new table中,然后再执行后续操作,oldbuckets中标记这个bucket已经被删除

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值