Go Map的底层原理解析之遍历流程和扩容流程
创作时间:
作者:
@小白创作中心
Go Map的底层原理解析之遍历流程和扩容流程
引用
CSDN
1.
https://m.blog.csdn.net/Ccccjjjj174/article/details/145691210
Go语言中的Map是一种非常常用的数据结构,它基于哈希表实现,提供了高效的键值对存储和访问。本文将深入解析Go Map的底层实现原理,重点介绍其遍历流程和扩容机制。
Map的随机遍历
在Go中,Map的遍历是通过迭代器(hiter)来实现的。迭代器结构体包含了遍历所需的各种信息:
type hiter struct {
key unsafe.Pointer // 指向遍历得到的key的指针
elem unsafe.Pointer // 指向遍历得到的value的指针
t *maptype // map类型,包含key、value类型大小等信息
h *hmap // map的指针
buckets unsafe.Pointer // map的桶数组
bptr *bmap // 当前遍历到的桶
overflow *[]*bmap // 新老桶数组对应的溢出桶
oldoverflow *[]*bmap // 旧桶数组的溢出桶
startBucket uintptr // 遍历起始位置的桶索引
offset uint8 // 遍历起始位置的key-value对索引
wrapped bool // 遍历是否穿越桶数组尾端回到头部
B uint8 // 桶数组的长度指数
i uint8 // 当前遍历到的key-value对在桶中的索引
bucket uintptr // 当前遍历到的桶
checkBucket uintptr // 因为扩容流程的存在,需要额外检查的桶
}
迭代器的初始化
迭代器的初始化过程主要通过mapiterinit函数完成:
func mapiterinit(t *maptype, h *hmap, it *hiter) {
it.t = t
if h == nil || h.count == 0 {
return
}
it.h = h
it.B = h.B
it.buckets = h.buckets
if t.bucket.ptrdata == 0 {
h.createOverflow()
it.overflow = h.extra.overflow
it.oldoverflow = h.extra.oldoverflow
}
// 通过随机数决定遍历的起始位置
var r uintptr
r = uintptr(fastrand())
it.startBucket = r & bucketMask(h.B)
it.offset = uint8(r >> h.B & (bucketCnt - 1))
// 记录迭代器状态
it.bucket = it.startBucket
// 确保没有并发写操作
if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
atomic.Or8(&h.flags, iterator|oldIterator)
}
mapiternext(it)
}
开始遍历
遍历过程主要通过mapiternext函数实现:
func mapiternext(it *hiter) {
h := it.h
if h.flags&hashWriting != 0 {
fatal("concurrent map iteration and map write")
}
t := it.t
bucket := it.bucket
b := it.bptr
i := it.i
checkBucket := it.checkBucket
next:
if b == nil {
// 已经遍历结束
if bucket == it.startBucket && it.wrapped {
it.key = nil
it.elem = nil
return
}
// 如果正在扩容
if h.growing() && it.B == h.B {
oldbucket := bucket & it.h.oldbucketmask()
b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
if !evacuated(b) {
checkBucket = bucket
} else {
b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
checkBucket = noCheck
}
} else {
b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
checkBucket = noCheck
}
// 去遍历下一个桶
bucket++
if bucket == bucketShift(it.B) {
bucket = 0
it.wrapped = true
}
i = 0
}
// 开始遍历每一个kv对
for ; i < bucketCnt; i++ {
offi := (i + it.offset) & (bucketCnt - 1)
if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {
continue
}
k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))
if t.indirectkey() {
k = *((*unsafe.Pointer)(k))
}
e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.elemsize))
// 处理扩容情况下的遍历顺序
if checkBucket != noCheck && !h.sameSizeGrow() {
if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
continue
}
}
// 如果未被迁移
if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||
!(t.reflexivekey() || t.key.equal(k, k)) {
it.key = k
if t.indirectelem() {
e = *((*unsafe.Pointer)(e))
}
it.elem = e
} else {
// 如果被迁移了,重新读取kv对
rk, re := mapaccessK(t, h, k)
if rk == nil {
continue // key has been deleted
}
it.key = rk
it.elem = re
}
it.bucket = bucket
if it.bptr != b { // avoid unnecessary write barrier; see issue 14921
it.bptr = b
}
it.i = i + 1
it.checkBucket = checkBucket
return
}
// 准备遍历下一个溢出桶
b = b.overflow(t)
i = 0
goto next
}
扩容流程
Go语言中的Map是基于哈希表实现的,当元素数量增加或删除频繁时,为了维持高效访问,会触发扩容机制。扩容分为增量扩容(正常扩容)和等量扩容(整理扩容)。
触发扩容的条件
扩容的触发条件主要通过负载因子来判断:
// loadFactor 装载因子,count 是map中元素的个数,(2^B)是 普通桶的数量
loadFactor := count / (2^B)
- 当元素数量超过负载因子(默认6.5)与当前桶数的乘积时触发增量扩容。此时,新桶数为原来的两倍(2×),以减少哈希冲突。
- 当溢出桶的数量过多时,触发等量扩容。这种情况通常由频繁插入和删除导致,桶内出现大量空闲槽位或长溢出链。此时桶数不变,但数据会被重新整理,减少溢出桶。
扩容流程
- 分配新桶
- 增量扩容:新桶数为旧桶数的两倍(2^B → 2^(B+1))。
- 等量扩容:新桶数与旧桶数相同(2^B保持不变)。
渐进式扩容
为了避免一次性迁移所有桶带来的性能瓶颈,Go的Map扩容采用了增量迁移(或称“懒迁移”)的策略:
- 在每次对Map进行查找、插入或删除操作时,运行时会顺带将一部分旧桶的数据迁移到新桶中。这意味着迁移工作分摊到了每个操作上,从而摊平了扩容的开销。
- 桶的迁移过程:对于旧桶中的每个键值对,都会重新计算其哈希值,然后根据新桶数组的大小确定新的桶索引。
- 顺序迁移
- 逐步推进:每次Map操作都会迁移少量旧桶,运行时更新
growIdx,直到整个旧桶数组的所有桶都被迁移完成。具体而言,growIdx从0开始,然后开始迁移hash值在0上面的kv对,最后growIdx++,去准备迁移下一个。 - 扩容完成:当
growIdx达到旧桶数组的长度时,说明所有数据都已经迁移到新桶中,此时旧桶数组会被废弃,Map完全过渡到新的哈希表结构中。
通过上述机制,Go语言的Map能够在保持高性能的同时,灵活应对数据量的变化,确保了数据访问的效率和稳定性。
热门推荐
色氨酸助眠神器,告别失眠烦恼
核磁扫描揭秘秦始皇陵:地宫结构与水银江河获证实
命由我作:从《了凡四训》看命运的自我改变
徐亚凤与“7501毛瓷”:将水点桃花技艺推向巅峰
“7501毛瓷”:从主席餐具到亿元收藏品
毛主席专用7501瓷器:工艺精湛,身价百万
崔致远:唐朝留学与儒学传播的传奇
崔致远的唐朝留学路,比你想象的还要“卷”
问道诸子|读懂荀子思想中突出的实践性
从《荀子》看中文学习:智慧如何点亮现代语言之路
苟姓“准爸爸”想给孩子改姓获官方回复,早在唐代就有人改姓荀
北京北海公园主要景点介绍,走进北海,感受北京的皇家气息!
QQ隐藏代码大全:表情、特效、快捷键全攻略
微信聊天打字慢?三种语音输入法让你一分钟打百字
运营策划书写作指南:从主题设定到高转化技巧
活性酶:让你的肠胃更嗨皮
ADH-1酶:抗衰神器还是科学骗局?
消化酶:人体代谢的秘密武器
现代复姓以欧阳氏为首,那诸葛、夏侯、司马三家又发展得如何?
理财规划助力轻松购车,你get了吗?
支撑位全解析:如何利用它做好股市交易
股市支撑位:把握买入时机与设置止损的关键
狼戈《苹果香》爆红,张艺谋点赞
狼戈《苹果香》爆红:一首歌,一段人生,一片深情
狼戈《苹果香》爆红,揭秘六星街背后的故事
挣脱名缰利锁,追求内心宁静:古诗词里的生活哲学
低调内敛沉默,方能成就内心强大
张衡、李虚中、徐子平:八字算命的三大里程碑
八字玄学新解:从性格分析到职业规划
揭秘八字命理:传统玄学的现代科学审视