Go 语言 map源码分析及图解(一)(查找、写入、删除K/V值)

本文深入解析Go语言中map的内部结构,包括hmap、bmap的数据结构,以及hash值定位、map创建、扩容机制、查找、写入和删除K/V值的过程。详细介绍了mapaccess1、mapassign和mapdelete等关键函数的实现,并探讨了负载因子和扩容策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


map扩容的源码分析见 下一节

map基本结构

hmap是map的核心数据结构:

type hmap struct {
   
	count     int // 当前的元素个数
	flags     uint8
	B         uint8  // 桶的数量为2的B次方,方便进行哈希的与运算
	noverflow uint16 // 溢出桶的数量
	hash0     uint32 // 哈希种子,计算哈希值使用

	buckets    unsafe.Pointer // 指向当前桶地址
	oldbuckets unsafe.Pointer // 扩容时指向旧桶
	nevacuate  uintptr        // 扩容进度计数器

	extra *mapextra // 该结构体包含溢出桶位置信息
}
type mapextra struct {
   
	overflow    *[]*bmap   //指向当前溢出桶首地址
	oldoverflow *[]*bmap   //指向旧的溢出桶首地址
	nextOverflow *bmap 	   //指向下一块可用的溢出桶
}

hmap结构图如下所示,bucketsoldbuckets 字段指向的都是一块连续的内存区域([ ]bmp)

桶bmap的结构体字段如下

type bmap struct {
   
	tophash [bucketCnt]uint8
}
//编译期间展开如下
type bmap struct{
   
	topbits 	[8]uint8     //用于表示标志位或hash值高八位来快速定位K/V位置
	keys		[8]keytype
	value		[8]valuetype
	pad			uintptr    //此字段go1.16.2版本已删除
	overflow 	uintptr    //连接下个bmap溢出桶
}

bmp结构体负责存储key/value值,结构图如下:

hash值定位K/V值

在哈希表中,寻找key/value值过程如下:

  1. key值会通过哈希函数得到hash值
  2. hash值与桶数-1(也就是2^B-1)进行与 操作得到所在桶的编号,找到相应的bmp
  3. 计算hash值高八位,与bmp中的tophash数组一一对比,找到偏移值
  4. 根据偏移值找到对应的key/value

整个流程如下图所示:

map创建

mp := make(map[int]int, 20)代码最终会调用 makemap函数,此函数负责对hmap结构体进行一系列初始化工作。

func makemap(t *maptype, hint int, h *hmap) *hmap {
   
	mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
	if overflow || mem > maxAlloc {
   
		hint = 0
	}
	if h == nil {
   
		h = new(hmap)
	}
	h.hash0 = fastrand()
	B := uint8(0)    //2^B为map实际的桶数
	for overLoadFactor(hint, B) {
      //根据make传入的预设值hint来计算B数值
		B++           
	}
	h.B = B   //最终2^B数值不会小于hint

	if h.B != 0 {
   
		var nextOverflow *bmap
		h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)  //桶的分配空间初始化操作
		if nextOverflow != nil {
               //处理有溢出桶的情况,B<4没有溢出桶
			h.extra = new(mapextra)
			h.extra.nextOverflow = nextOverflow    //指针指向第一块溢出桶
		}
	}
	return h
}

计算桶的数量

overLoadFactor函数根据make传入的预设值来计算B的值

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也就是哈希表的负载因子
}

申请buckets内存空间

makeBucketArray函数负责给桶申请一段连续的内存空间

func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
   
	base := bucketShift(b)    //bucketShift计算2^b的值,base就是桶的总数
	nbuckets := base
	if b >= 4 {
             //大于4才使用溢出桶
		nbuckets += bucketShift(b - 4)   //溢出桶数量为2^(b-4)
		sz := t.bucket.size * nbuckets
		up := roundupsize(sz)
		if up != sz {
   
			nbuckets = up / t.bucket.size
		}
	}

	if dirtyalloc == nil {
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值