go语言map底层及扩容机制原理详解(下)

【Go】深入理解 Go map:赋值和扩容迁移 ① Map 的赋值难点在于数据的扩容和数据的搬迁操作。扩容不是一定会新增空间,也有可能是只是做了内存整理。tophash 的标志即可以判断是否为空,还会判断是否搬迁,以及搬迁的位置为X or Y。delete map 中的key,有可能出现很多空的kv,会导致搬迁操作。如果可以避免,尽量避免。 阅读详情

前言

上文对Go map的底层数据结构有所了解,并对其扩容机制的步骤进行简略的描述。本文将会详细地去解释Go map扩容机制的详细原理。

1. 触发扩容操作

在go语言中,当我们插入一个元素到hmap时,会有以下两种情况:

  1. 若元素存在,则更新元素的val
  2. 若元素不存在,则将该元素插入到map中。

此处的插入操作并不是简单的找到一个空余空间插入,而是在插入之前,要先判断map是否需要扩容以及是否正在进行扩容操作。
因为当map非常大的情况下,每次迁移大量的数据,会出现长时间的暂停。在go1.8版本以后,这个步骤采用来分批迁移的策略:即每次向map添加新元素或查找时,都会迁移一小部分元素,避免长时间的暂停。

// 如果我们达到了最大负载因子×容量的阈值,或者我们有太多的溢出桶,
// 并且我们还没有处于增长中,那么开始增长。
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
	hashGrow(t, h)
	goto again 
}

// growing 报告 h 是否正在扩容。扩容可能是到相同的大小或更大。
// 通过判断oldbuckets是否为nil来判断是否扩容完成
func (h *hmap) growing() bool {
	return h.oldbuckets != nil
}

因此,当进行查询或插入操作时,若map的元素数量超过了负载因子×容量的阈值或太多的桶溢出且没有正在发生扩容操作,就会触发扩容。

2. 触发扩容的条件

上文我们分析了触发扩容操作需要达到负载因子和容量乘积的阈值或桶溢出过多。那么它的底层到底是如何具体进行判断实现的呢?
Go的底层主要内置了两个函数来判断,分别是overLoadFactor和tooManyOverflowBuckets1:

const (
	// 一个桶可以容纳的键/元素对的最大数量。
	bucketCntBits = 3
	bucketCnt     = 1 << bucketCntBits // 相当于2^3 = 8

	// 触发增长的桶的最大平均负载是6.5。
	// 表示为 loadFactorNum/loadFactorDen,以允许使用整数数学运算。
	loadFactorNum = 13
	loadFactorDen = 2
)
// bucketShift 返回 1<<b,为了优化代码生成。
func bucketShift(b uint8) uintptr {
	// 通过掩码处理移位数量,可以省略溢出检查。
	return uintptr(1) << (b & (goarch.PtrSize*8 - 1))
}

// overLoadFactor 报告将 count 个项放置在 1<<B 个桶中是否超过负载因子。
func overLoadFactor(count int, B uint8) bool {
	// 如果 count 大于每个桶能容纳的元素数量(bucketCnt),并且
	// count 大于负载因子允许的最大元素数量(loadFactorNum*(bucketShift(B)/loadFactorDen)),
	// 则返回 true,表示超过负载因子。
	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}

// tooManyOverflowBuckets 报告对于具有 1<<B 个桶的 map 而言,noverflow 个溢出桶是否太多。
// 注意这些溢出桶大部分必须在稀疏使用中;
// 如果使用密集,那么我们已经触发了常规的 map 增长。
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
	// 如果阈值过低,我们会做额外的工作。
	// 如果阈值过高,那么增长和收缩的 map 可以保留大量未使用的内存。
	// “太多”意味着(大约)与常规桶一样多的溢出桶。
	// 有关更多详细信息,请参阅 incrnoverflow。
	if B > 15 {
		B = 15
	}
	// 编译器在这里看不到 B < 16;掩蔽 B 以生成更短的移位代码。
	return noverflow >= uint16(1)<<(B&15)
}

正在速更…

go语言DAY7 字典Map 指针 结构体 函数 这样就把所有散列的map元素按哈希值与队列数的模值这样的规律放好,将来想找到某一个map元素,就根据模值找到这个元素所在的队列,在根据这个元素的哈希值具体查找到这个元素,这样的话效率对比查找某个map值需要遍历整个map集合来说效率就高了3/4。哈希值与B的&运算,但是B值与哈希值相比较高位全部为0,高位&运算的二进制结果都为0,没有意义,通常取哈希值低B位进行运算得到对应标准桶的索引下标。也不能是嵌套的切片,map类型。bmap代表的是桶数组中的桶对象,bmap负责存储key,value值。 阅读详情

相关推荐

Golang Map 深度剖析:原理、实践与面试要点

嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map。在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章就带你走进 Go 语言 Map 那个神秘的世界,帮你一层一层揭开它的面纱!从底层的原理,到最佳实践,再到高频面试题的分析,这篇文章会从各个方面满足你的求知心。

xuyi19921216的博客 2692

Golang Map的扩容机制(1.20.3版本)

博客专注于深入探索Golang的Map扩容机制(1.20.3版本)。首先,我们详细地探讨了引发扩容操作的两个主要条件:overLoadFactor和tooManyOverflowBuckets,并特别解释了“溢出桶太多”的状况何时会出现。进入准备进行扩容的阶段,我们详细解读了flags字段的变动。在探讨扩容函数部分,我们深入分析了growWork和evacuate,特别关注了noldbuckets和它的子部分。此外,我们提供了一个独特的小结部分,方便读者回顾和理解所学的内容。文章最后侧重于描绘扩容的

倪灵-ziop 3526

在F1C100s上跑GBA游戏:手把手教你用Buildroot配置SDL和移植gpsp模拟器

本文详细介绍了在全志F1C100s开发板上配置Buildroot、移植SDL库和gpsp模拟器的完整流程,帮助开发者实现在ARM9平台上流畅运行GBA游戏。内容涵盖环境配置、交叉编译、常见问题解决及性能优化技巧,特别针对'段错误'和'无声音'等典型问题提供实用解决方案。

weixin_42566424的博客 396

Go语言Map的扩容

浅析Go语言中Map的扩容操作。Briefly analyze

Wrysunny__的博客 2664

Rust嵌入式开发环境搭建指南(基于Stm32+Vscode)

本文档旨在指导开发者如何搭建基于Rust语言的STM32嵌入式开发环境。相比传统的C/C++开发方式,Rust提供了更强的内存安全保障和现代化的语言特性,非常适合嵌入式系统开发。本指南将详细介绍从环境搭建到项目配置的全过程,特别关注VSCode作为IDE的配置以及STLink和DAPLink调试器的使用。

u014627020的博客 2601

2021年全国职业院校技能大赛高职组网络系统管理赛项

S1 Ruijie(config)#switch virtual domain 1 Ruijie(config-vs-domain)#switch 1 Ruijie(config-vs-domain)#switch 1 priority 150 Ruijie(config-vs-domain)#switch 1 description S1 Ruijie(config-vs-domain)#exit Ruijie(config)#vsl-port Ruijie(config-vsl-port)#po

bfq05234214的博客 1133

Go语言中Map的自动扩容机制

哈希表是用空间换时间的结构,但是在扩容是会非常耗时,因此在对时间性能有要求的情况下,应该预估map设定的大小,尽量避免扩容操作。的扩容操作,扩容操作会重新分配更大的哈希表,将原有的键值对重新哈希到新的哈希表中。在触发扩容后,Go 会创建一个新的哈希表,其大小默认是当前哈希表大小的两倍。中的元素数量达到当前哈希表大小的 6.5 倍时,Go 会触发扩容操作。遍历原有的哈希表,将每个键值对重新计算哈希值,并插入到新的哈希表中。扩容完成后,释放原有的哈希表内存空间,将新的哈希表作为。扩容完成后,可以继续往。

weixin_73075352的博客 722

【01、Go与VSCode的安装】

小白学Go语言

wangruil的博客 1536

深入理解 Go 语言中的 map 扩容机制

Go 语言中的 map 使用一种称为渐进式扩容(incremental rehashing)的技术来避免扩容过程中导致的性能抖动。Go 语言中的 map 提供了一种高效的键值对存储和查找方式,其底层的扩容机制保证了在大数据量情况下的性能。在这个示例中,我们向 map 中添加了 1000 个元素,然后再添加一个新元素,触发 map 的扩容。通过观察 map 的大小变化,我们可以看到扩容的效果。当 map 触发扩容时,会创建一个新的更大的哈希表,并将旧哈希表中的元素重新哈希并迁移到新哈希表中。

qq_45811217的博客 935

【硬件工程师面经整理18_Smith圆图】

若文中任何内容侵犯了您的合法权益,请相关权益人及时与我联系,我将立即进行核实并采取适当措施,包括但不限于删除相关内容或更新文章以消除影响。**在Smith圆图上,源阻抗表示为圆心,而负载阻抗表示为圆图上的一个点。具体来说,通过将负载阻抗在Smith圆图上移动到特定位置,使其与传输线特性阻抗形成纯电阻或纯电抗的组合,从而实现匹配。例如,反射系数表示为Smith圆图上的一个点,而驻波比则表示为圆图上两个点之间的距离。Smith圆图是用于阻抗匹配和网络分析的工具,它是一种将复阻抗表示为坐标的图形。

LeeYLong的博客 651

ARM功耗管理之功耗状态及功耗模式

思考:功耗状态?功耗模式?Core&Cluster&Device&SoC如何映射?

AI-安全-功耗 780

go语言map底层及扩容机制原理详解(上)

在顺序结构以及平衡树中,元素与其的存储位置之间没有对应关系,因此查找一个元素时,必须进行多次比较。所以顺序查找的时间复杂度为O(N),而平衡树中则为树的高度O(log2N)。为了减少搜素时元素比较的次数,是否有一种方法可以不经过任何比较,通过元素的存储位置与它的关键码以O(1)的时间复杂度直接找到该元素呢?哈希表就是通过某种函数(hash)来使元素的存储位置和其元素值之间建立一一映射的关系,那么就可以通过这种关系快速找到该元素。(数组就是一种简单的哈希表)要提供合适的初始容量。

codekingo的博客 1360

1 硬件工程师笔面试高频知识考点真题及解析——电阻

本文系统介绍了电阻的基础知识、选型要点及各类特殊电阻的应用。主要内容包括:1. 电阻选型需考虑阻值、封装、功率、精度、耐压等参数;2. 高频信号优先选用小封装电阻以减小寄生参数;3. 采样电阻需高精度、低温漂以保证测量准确性;4. 详细解析了上拉/下拉电阻、0欧电阻、磁珠等元件的特性与区别;5. 介绍了压敏电阻、热敏电阻等特殊电阻的工作原理及应用场景;6. 阐述了阻抗匹配的重要性及常见匹配方法。文章为电子工程师提供了全面的电阻选型与应用指南。

XU157303764的博客 2834

Go语言map的底层原理

hmap结构hmap是map的顶层结构,包含了管理map操作所需要的各类元数据。(比如哈希种子,桶的数量,桶的地址)其次hmap通过buckets字段(指针类型)引用了所有的bucket。buckets是一个指向bucket数组的指针,数组中的每一个元素就是一个bmap。bmap结构bamp是实际存储键值对的结构。每一个bamp可以存储多个键值对,并且包含溢出指针,用来处理哈希冲突的情况。bmap结构内的数组包含了多个键值对槽位 (key/val。

2303_79710813的博客 1578

QEMU源码全解析7 —— QEMU主函数

QEMU源码全解析7 —— QEMU主函数

phmatthaus的专栏 1292

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

关于Go语言的底层,Slice,map

Go map 默认是并发不安全的,同时对 map 进行并发读写的时,程序会 panic,原因如下:Go 官方经过长时间的讨论,认为 map 适配的场景应该是简单的(不需要从多个 gorountine 中进行安全访问的),而不是为了小部分情况(并发访问),导致大部分程序付出锁的代价,因此决定了不支持。map 在扩缩容时,需要进行数据迁移,迁移的过程并没有采用锁机制防止并发操作,而是会对某个标识位标记为 1,表示此时正在迁移数据。

viviiuw的博客 1023

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Golang Map简介

map_var := make(map[K]V) // 用make函数创建一个空的map,其中K和V分别为键和值的类型map_var[key] = value // 向map中添加一个键值对value := map_var[key] // 获取指定键的值delete(map_var, key) // 从map中删除指定的键及其对应的值本节我们学习了Go语言中的map数据类型,使用方法以及map的数据安全问题。

fronge的博客 1228

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

【go】go语言slice/map的产生背景,及原理理解

先讲一下slice的产生背景,首先,go本身是有数组的,但是不会自动扩容,然而实际工作中会有很多场景是要求能自动扩容的,比如说你接用户的数据,你肯定要设置一个可以自动扩容的数组来接,因为你没办法预测他会传多大的数据,提前设置的数组过大或者过小都不合适,因此,go的slice应运而生slice结构包含一个指向底层数组的指针长度容量。这样的设置就可以看出他是一个轻量级的容器,因为实际上只是一个对底层数组的引用。slice的扩容机制,在长度超过容量的时候,如果容量<1024那么双倍。

m0_74282926的博客 1642

Go语言入门之Map详解

name:变量名key_type:键的类型value_type:值的类型// 方式一// 方式二1 : "老一",2 : "老二",3 : "老三",// 方式三:5代表容量,也就是在内存中占用多大的空间,可以省略。

clisks的博客 1596

Go map 底层,以及扩容

总结笔记《Go语言精进之路》

小目标 1294

golang map原理

先说结论,在 golang 中 map 是无序的,准确的说是无法严格保证顺序的, 从上面的源码中我们可以知道,golang 中 map 在扩容后,可能会将部分 key 移至新内存,由于在扩容搬移数据过程中,并未记录原数据位置, 并且在 golang 的数据结构中也并未保存数据的顺序,所以那么这一部分在扩容后实际上就已经是无序的了。不难看出,即使不对 map 进行扩容,在多次遍历时也是无序的,这是因为 golang 官方在设计时故意加上随机的元素,将遍历 map 的顺序随机化,用来防止使用者用来顺序遍历。

阿拉斯加大闸蟹的博客 1万+

GoLang之map底层系列三(书面版)

GoLang之map底层系列二(深度学习)

weixin_52690231的博客 2693

12 Go语言map底层浅析

Go语言map底层浅析 文章目录Go语言map底层浅析1、Hash函数2、map 源码2.1hmap(a header of map)2.2 bmap(a bucket of map)3、hmap和bmap结构图4、map的扩容5、结语 笼统的来说,go的map底层是一个hash表,通过键值对进行映射。 键通过哈希函数生成哈希值,然后go底层的map数据结构就存储相应的hash值,进行索引,最终...

小人物、平凡,世界很大我只想在家 509

Go【Map】进阶篇:底层原理、赋值、访问、扩容流程

Go语言中map的底层实现采用哈希表结构,主要由hmap和bmap组成。hmap是map的核心控制结构,包含桶数组、哈希种子等元信息;bmap是实际存储键值对的桶结构,每个桶可存8个键值对,采用紧凑的内存布局存储tophash、keys和values数组,并通过overflow指针连接溢出桶。map初始化时会根据容量计算桶数量,写入数据时通过哈希值定位桶位置,并处理可能的扩容迁移。这种设计在保证高效查找的同时,通过溢出桶减少了扩容频率。

qq_49232751的博客 1139
上一篇: 计算机网络第一章-计算机网络的基本认识
下一篇: 计算机网络第二章-物理层
看繁星aa
博客等级 码龄4年 922粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值