go map的实现

本文探讨了Go语言中map的实现原理,包括其内存布局、并发不安全性以及查询、初始化、插入数据和扩容的过程。通过对hmap和bucket结构的分析,揭示了map[int]int与map[string]string类型的桶大小差异,并提供了查询代码示例。

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

一直很好奇golan的map在运行过程中是以一个什么样的形式存在的,为什么并发不安全,抽空研究了下golang的源码,做个笔记。
map 的存在形式有两部分组成 hmap 和bucket 。
hmap的结构如下

	type hmap struct {
		  count int 	//	map的大小
		  flags	unit8		//	
		  B  	unit8    // 用来计算map的bucket大小  map的bucket大小(2^B)
		  noverflow unit16   //	溢出桶的大小  详细见 incrnoverflow 
		  hash0       unit32   //  hash 的种子
		  buckets     unself.Pointer   //	指向buckects数组的地址,可能为nil
		  oldbuckets unself.Pointer  //  迁移用的(待补充)
		  nevacuate    uintptr		//	装载因子,用来判断扩容
		  extra  * mapextra		//	hmap的额外信息,其中mapextra.nextOverflow指向预分配溢出桶的地址,当预分配用完之后变成nil
	} 

bucket的结构如下

	type bmap struct {
			tophash       [buckectCnt]uint8	// key hash的高8位
	}

注意: bucket 不仅仅放的是tophash 数组 紧接着是k1 k2 k3 … k8 v1 v2 v3 …v8 再接着是个指针(指针指向下一个溢出桶的地址) k1 k2 … v1 v2 为了避免内存的对齐, k v 的大小大于128字节 则存储的是指针
疑问 对于map[int]int 这类的map桶的大小是固定的 但是对于一个map[string]string 如何分配桶的大小

查询代码如下

	hash := alg.hash(key, uintptr(h.hash0))   //  key 的hash值
	m := bucketMask(h.B)           //  hash 基数
	b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))  // hash&m定位到第几个桶 根据桶大小算出对应桶的地址
	if c := h.oldbuckets; c != nil {      //   是否在搬迁 算出桶的地址(逻辑待确定)
		if !h.sameSizeGrow() {  //	使用旧桶的hash 基数
			m >>= 1
		}
		oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
		if !evacuated(oldb) {       //	判断当前桶是否已经搬迁
			b = oldb
		}
	}
	top := tophash(hash)   //  高8位
	for ; b != nil; b = b.overflow(t) {      // 循环链表 直到溢出桶的地址为空 注意 overflow的定义   桶的大小-指针的大小
		for i := uintptr(0); i < bucketCnt; i++ {       //  遍历桶的tophash 跟 top 做对比 判断元素在桶的位置
			if b.tophash[i] != top {
				continue
			}
			k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))  // 获取k的值  注意dateOffset = [8]unit8 = 64位 正好是tophash的大小
			if t.indirectkey {   // 如果k是个指针 则 寻找对应的值
				k = *((*unsafe.Pointer)(k))
			}
			if alg.equal(key, k) {
				v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
				if t.indirectvalue {
					v = *((*unsafe.Pointer)(v))
				}
				return v
			}
		}
	}
	return unsafe.Pointer(&zeroVal[0])

现在可以画出map的内存布局图
在这里插入图片描述

初始化map

h.hash0 = fastrand()		//	加入随机因子, 每个map 随机因子不同
// 选择合适的B
B := uint8(0)
for overLoadFactor(hint, B) {
	B++
}
h.B = B
//	分配 内存空间 如果B 不为零
if h.B != 0 {
	var nextOverflow *bmap
	h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
	if nextOverflow != nil {
		h.extra = new(mapextra)
		h.extra.nextOverflow = nextOverflow
	}
}
  return h
  }

	func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
	base := bucketShift(b)  //	计算出桶的数量
	nbuckets := base
	//	小于16个桶不分配溢出桶位置
	if b >= 4 {
		nbuckets += bucketShift(b - 4)  //	计算出溢出桶的个数
		sz := t.bucket.size * nbuckets
		up := roundupsize(sz)
		if up != sz {                             // 看看内存是否允许分配这么大的内存块
			nbuckets = up / t.bucket.size
		}
	}
	//	分配存 如果有回收的内存用回收的内存
	if dirtyalloc == nil {
		buckets = newarray(t.bucket, int(nbuckets))
	} else {
		buckets = dirtyalloc
		size := t.bucket.size * nbuckets
		if t.bucket.kind&kindNoPointers == 0 {
			memclrHasPointers(buckets, size)
		} else {
			memclrNoHeapPointers(buckets, size)
		}
	}
	//	如果有溢出桶  则nextOverflow指针指向溢出桶的位置
	if base != nbuckets {
		nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))   // nextOverflow指针指向溢出桶的位置
		last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))   // 最后一个溢出桶改的位置
		last.setoverflow(t, (*bmap)(buckets))                //	设置最后一个溢出桶的ptr 指向buckets的位置 形成一个环形 (在判断预分配的溢出桶是否已经使用完)
	}
	return buckets, nextOverflow
	}

插入数据

// 判断是否正在写入
if h.flags&hashWriting != 0 {
	throw("concurrent map writes")
}
// 获取hash 值
alg := t.key.alg
hash := alg.hash(key, uintptr(h.hash0))
//	设置正在写
h.flags |= hashWriting

again:
	bucket := hash & bucketMask(h.B) //	找到第几个桶
	if h.growing() {							//	是否在扩容
		growWork(t, h, bucket)        //  搬迁当前桶 以及对应的溢出桶
	}
	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))  //	获取当前桶的地址
	top := tophash(hash) 

var inserti *uint8
var insertk unsafe.Pointer
var val unsafe.Pointer
for {
	for i := uintptr(0); i < bucketCnt; i++ {
		if b.tophash[i] != top {
			if b.tophash[i] == empty && inserti == nil {   // 找到第一个空的位置记
				inserti = &b.tophash[i]
				insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
				val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
			}
			continue
		}
		k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))        // 非空 update
		if t.indirectkey {
			k = *((*unsafe.Pointer)(k))
		}
		if !alg.equal(key, k) {                 //判断key 值是否相等
			continue
		}
		if t.needkeyupdate {
			typedmemmove(t.key, k, key)
		}
		val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
		goto done
	}
	ovf := b.overflow(t)
	if ovf == nil {
		break
	}
	b = ovf
}
//	未找到key并且桶已满  判断是否需要扩容 然后然后在新的map 寻找插入位
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
	hashGrow(t, h)    // 扩容
	goto again // Growing the table invalidates everything, so try again
}
//	桶满 分配一个新的桶
if inserti == nil {
	// all current buckets are full, allocate a new one.
	newb := h.newoverflow(t, b)
	inserti = &newb.tophash[0]
	insertk = add(unsafe.Pointer(newb), dataOffset)
	val = add(insertk, bucketCnt*uintptr(t.keysize))
}
if t.indirectkey {
	kmem := newobject(t.key)
	*(*unsafe.Pointer)(insertk) = kmem
	insertk = kmem
}
if t.indirectvalue {
	vmem := newobject(t.elem)
	*(*unsafe.Pointer)(val) = vmem
}
typedmemmove(t.key, insertk, key)  	//	疑问 这个作用是什么
*inserti = top
h.count++

done:
if h.flags&hashWriting == 0 {
	throw("concurrent map writes")
}
h.flags &^= hashWriting
if t.indirectvalue {
	val = *((*unsafe.Pointer)(val))
}
return val

返回 val 的地址,隐藏了最后一步写入动作(将值拷贝到指定内存区域)是通过底层汇编配合来完成的,在 runtime 中只完成了绝大部分的动作。

扩容

bigger := uint8(1)
if !overLoadFactor(h.count+1, h.B) {   // 判断是否需要调整大小
	bigger = 0
	h.flags |= sameSizeGrow
}
oldbuckets := h.buckets         //	当前bucket 赋给old
newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil) // 分配内存地址
flags := h.flags &^ (iterator | oldIterator)
if h.flags&iterator != 0 {
	flags |= oldIterator
}
h.B += bigger
h.flags = flags
h.oldbuckets = oldbuckets
h.buckets = newbuckets
h.nevacuate = 0
h.noverflow = 0

if h.extra != nil && h.extra.overflow != nil {
	if h.extra.oldoverflow != nil {
		throw("oldoverflow is not nil")
	}
	h.extra.oldoverflow = h.extra.overflow
	h.extra.overflow = nil
}
if nextOverflow != nil {
	if h.extra == nil {
		h.extra = new(mapextra)
	}
	h.extra.nextOverflow = nextOverflow
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值