Go 语言 map源码分析及图解 (二)(map扩容)


map基本的结构及查找、写入、删除K/V值的具体分析见 上一节

扩容总流程

  1. mapassign函数写入K/V值后进行扩容条件判断:
    (1)负载因子大于6.5,也就是元素总数 / 总桶数 > 6.5时,触发翻倍扩容
    (2)溢出桶过多,一般发生于持续写入数据又全部删除时,触发等量扩容
  2. hashGrow函数负责创建新桶,分配内存空间,注意此时并没有进行旧桶的数据迁移,而是把迁移操作分散到每一次写入和删除中,也就是渐进式rehash,防止迁移大量数据时导致的查询服务停止,保证哈希表性能的稳定性。
  3. 在扩容状态中,每一次mapassign函数写入和mapdelete函数删除操作时都会触发两次旧桶的数据迁移
    (1)当前写入或删除key值对应的旧桶
    (2)nevacuate计数值对应的旧桶,每次数据迁移后nevacuate+1,如果和上面的旧桶相同则更新迁移进度,nevacuate == 旧桶总数时结束扩容状态。
  4. evacuate函数负责对旧桶数据进行迁移,翻倍扩容时旧桶数据会分流到新桶中不同的编号桶中。

map基本结构及hash值查找流程

bmap基本结构如下:

hash值定位K/V值位置如下:

扩容触发条件

mapassign函数每次写入K/V数据后都会进行触发扩容条件的检测:

if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
   
   
		hashGrow(t, h)   //进行扩容操作,创建新桶并让h.buckets指向新桶内存地址
		goto again // Growing the table invalidates everything, so try again
	}

包括以下两个方面的检测:

  1. 负载因子大于6.5,也就是元素总数 / 总桶数 > 6.5时,触发翻倍扩容
  2. 溢出桶过多,一般发生于持续写入数据又全部删除时,触发等量扩容

overLoadFactor函数检测负载因子:

loadFactorNum = 13
loadFactorDen = 2
bucketCntBits = 3
bucketCnt     = 1 << bucketCntBits    //等价于8
func bucketShift(b uint8) uintptr {
   
   
	return uintptr(1) << (b & (sys.PtrSize*8 - 1))  //就是返回2的b次方数值
}
func overLoadFactor(count int, B uint8) bool {
   
   
	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
	//等价于:count > 8 && count > 13*(2^B / 2)
	//把位置换下,count/2^B > 6.5   6.5也就是哈希表的负载因子
}

tooManyOverflowBuckets函数检测溢出桶数量

func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
   
   
	if B > 15 {
   
   
		B = 15
	}
	//判断溢出桶是否太多,当桶总数 < 2 ^ 15 时,如果溢出桶总数 >= 桶总数,则认为溢出桶过多。
	//当桶总数 >= 2 ^ 15 时,直接与 2 ^ 15 比较,当溢出桶总数 >= 2 ^ 15 时,即认为溢出桶太多了。
	return noverflow >= uint16(1)<<(B&15)
}

新桶创建(hashGrow)

hashGrow函数负责扩容操作,该函数创建新桶并让h.buckets指向新桶内存地址,注意此时并没有进行数据迁移的工作。

func hashGrow(t *maptype, h *hmap) {
   
   
	bigger := uint8(1)
	if !overLoadFactor(h.count+1, h.B) {
   
    //溢出桶太多造成扩容的情况
		bigger = 0    	//等量扩容,下面h.B += 0
		h.flags |= sameSizeGrow    //等量扩容标志位
	}
	oldbuckets := h.buckets   //oldbuckets保存当前桶地址
	newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)  //为新桶申请一段连续的内存空间

	flags := h.flags 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值