go专家编程系列(4)常见数据结构 map

map



hash与buckets

说到键值对的存储,我们就会想到哈希表,哈希表通常会有一堆桶来存储键值对,一个键值对来了自然要存到一个桶中。首先将key通过hash()处理一下得到一个hash值,现在要利用这个hash值从m个桶中选择一个,桶的编号区间 [0,m-1] 。
在这里插入图片描述

取模法

hash%m

与运算法

hash & (m-1)

想要使用与运算法就要限制桶的个数 m 必须是2 的整数次幂,这样 m 的二进制表示一定只有一位为1,(m-1) 就是除了最高为其他均为 1 ,避免一些桶绝对不会被选中的情况

如果之后有其他键值对也选择了同一个桶,就是发生了哈希冲突,解决方案一般是 开放地址法拉链法

  • 开放地址法 就是在发生冲突的桶后找没有被占用的桶来存放键值对,查找时定位到桶后hash不匹配,就继续向下找直到遇到空桶。
  • 如图
    在这里插入图片描述

解决hash冲突

  • 发生hash冲突会影响hash表的读写效率,选择散列均匀的 hash() 可以减少hash冲突的发生
  • 适时对hash表进行扩容意识保障读写效率的有效手段,通常会把(count of key : value) / m作为是否需要扩容的判断依据,这个比值被称作负载因子

当需要扩容时,就要分配更多的桶,将旧桶中的数据迁移时,为了避免一次性迁移大量数据带来的性能损耗,通常会在hash表扩容时先分配足够多的新桶,然后用一个字段记录旧桶的位置,再加一个字段记录旧桶迁移的进度(记录下一个要迁移的旧桶编号),在每次hash表读写操作时,如果检测到当前正处于扩容阶段就完成一部分键值对迁移任务,直到所有的键值对迁移完成,旧桶不再使用,算完成了一次扩容操作,这就是所谓渐进式扩容

map in golang

src/runtime/map.go:hmap

type hmap struct {
	count     int // 已经存储的键值对数目
	flags     uint8
	B         uint8  // 桶的数目是 2^B golang使用了与运算法
	noverflow uint16 // 溢出桶数量
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // 桶在哪儿
	oldbuckets unsafe.Pointer // 扩容阶段的旧桶位置记录
	nevacuate  uintptr        // 扩容时,下一个要迁移的旧桶编号

	extra *mapextra // optional fields
}

再来看看map用的桶长什么样。也就是 bmap

  • 一个桶中可以存放8个键值对,但是为了让内存排列更加紧凑,采用 8个键+8个值 的存放方式,在键值对之上是8个 tophash 每个tophash都是对应hash值的高8位,最后的 voerflowbmap指针指向溢出桶溢出桶的结构与常规桶相同,是为了减少扩容次数而引入的。当一个桶存满了,还有可用的溢出桶是时,就会在同种桶后链接一个溢出桶,实际上,如果哈希表要分配的桶的数目大于2^4时就认为使用到溢出桶的几率较大,就会预分配 2^(B-4)个溢出桶备用,这些溢出桶和常规桶在内存中是连续的 ,前 2^B用作常规桶,后面的用于溢出桶
type mapextra struct {

	overflow    *[]*bmap   // 目前已经被使用的溢出桶的地址
	oldoverflow *[]*bmap // 扩容阶段存储存储旧桶用到的那些溢出桶的地址

	nextOverflow *bmap  //下一个空闲溢出桶
}

在这里插入图片描述

在这里插入图片描述
如果将这个桶存满的话,接下来再继续存储新的键值对时,这个hash表是会创建溢出桶还是会发生扩容呢?

map的扩容规则

Go语言的map默认负载因子(count / (2^B))是 6.5 (即平均每个buckets存储的键值对达到6.5个以上),超过这个值就会发生翻倍扩容,分配新桶的数目是旧桶的两倍

  • hamp中的buckets指向新分配的两个桶
  • oldbuckets指向旧桶,bevacuate为0表示接下来要迁移编号为0的旧桶
  • 过程如图所示
    在这里插入图片描述
    还有一种情况也会触发扩容,就是负载因子没有超标,但是溢出桶使用较多。
  • B <= 15 noveflow > 2^B
  • B > 15 neverflow >= 2^15

这种情况针对的是在某些极端情况下,例如经过大量的元素增删后,兼职对刚好集中在一小部分buckets中,这时会发生等量扩容,即buckets不变,重新做一次类似增量扩容的迁移动作,将松散的键值对重新排列一次。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值