golang map真有那么随机吗?——map遍历研究
在随机选取map中元素时,本想用map遍历的方式来返回,但是却并没有通过测试。
那么难道map的遍历并不是那么的随机吗?
以下代码参考go1.18
hiter是map遍历的结构,主要记录了当前遍历的元素、开始位置等来完成整个遍历过程
// A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go
// and reflect/value.go to match the layout of this structure.
type hiter struct {// 指向下一个遍历key的地址key unsafe.Pointer // Must be in first position. Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).// 指向下一个遍历value的地址elem unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).// map类型t *maptype// map headerh *hmap// 初始化时指向的bucketbuckets unsafe.Pointer // bucket ptr at hash_iter initialization time// 当前遍历到的bmapbptr *bmap // current bucketoverflow *[]*bmap // keeps overflow buckets of hmap.buckets aliveoldoverflow *[]*bmap // keeps overflow buckets of hmap.oldbuckets alive// 开始桶startBucket uintptr // bucket iteration started at// 桶内偏移量offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)// 是否从头遍历了wrapped bool // already wrapped around from end of bucket array to beginningB uint8// 正在遍历的槽位i uint8// 正在遍历的桶位bucket uintptr// 用于扩容时进行检查checkBucket uintptr
}
mapiterinit为开始遍历的方法,主要是确定初始遍历的位置
// mapiterinit initializes the hiter struct used for ranging over maps.
// The hiter struct pointed to by 'it' is allocated on the stack
// by the compilers order pass or on the heap by reflect_mapiterinit.
// Both need to have zeroed hiter since the struct contains pointers.
func mapiterinit(t *maptype, h *hmap, it *hiter) {// 若map为空,则跳过遍历过程it.t = tif h == nil || h.count == 0 {return}if unsafe.Sizeof(hiter{})/goarch.PtrSize != 12 {throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go}it.h = h// grab snapshot of bucket state// 迭代器快照记录map桶信息it.B = h.Bit.buckets = h.bucketsif t.bucket.ptrdata == 0 {// Allocate the current slice and remember pointers to both current and old.// This preserves all relevant overflow buckets alive even if// the table grows and/or overflow buckets are added to the table// while we are iterating.h.createOverflow()it.overflow = h.extra.overflowit.oldoverflow = h.extra.oldoverflow}// decide where to start// 开始bucket选择随机数的低B位// 偏移量选择随机数高B位与桶数量,显然这个桶数量是不包括溢出桶的r := uintptr(fastrand())if h.B > 31-bucketCntBits {r += uintptr(fastrand()) << 31}it.startBucket = r & bucketMask(h.B)it.offset = uint8(r >> h.B & (bucketCnt - 1))// iterator state// 更新迭代器桶为初始桶it.bucket = it.startBucket// Remember we have an iterator.// Can run concurrently with another mapiterinit().// 标记可能有迭代正在使用桶和旧桶if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {atomic.Or8(&h.flags, iterator|oldIterator)}mapiternext(it)
}
从上面的代码分析我们便可以看出随机选取的元素并不是真的随机,溢出桶并不包含在随机选择的范围里面
在具体的遍历过程,存在以下疑问
- 如果在扩容中,如何进行遍历?
- 如何保证不遗漏?
- 如何防止重复遍历?
func mapiternext(it *hiter) {h := it.h// 如果标记已经写入,则抛出并发迭代写入错误if h.flags&hashWriting != 0 {throw("concurrent map iteration and map write")}t := it.tbucket := it.bucketb := it.bptri := it.icheckBucket := it.checkBucketnext:if b == nil {// 如果再次遇到开始bucket且是从头遍历的,则说明迭代结束,返回if bucket == it.startBucket && it.wrapped {// end of iterationit.key = nilit.elem = nilreturn}// 如果正在迁移过程中,且老桶没被迁移,采用老桶if h.growing() && it.B == h.B {// Iterator was started in the middle of a grow, and the grow isn't done yet.// If the bucket we're looking at hasn't been filled in yet (i.e. the old// bucket hasn't been evacuated) then we need to iterate through the old// bucket and only return the ones that will be migrated to this bucket.oldbucket := bucket & it.h.oldbucketmask()b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))// bucket未迁移,记录bucket// checkBucket在当前map处于迁移而bucket未迁移时,为当前bucket// 否则为noCheckif !evacuated(b) {checkBucket = bucket} else {b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))checkBucket = noCheck}} else {// map处于未迁移,或者bucket迁移完成,采用新桶b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))checkBucket = noCheck}// 推进到下一桶bucket++// 遍历到最后一个桶,要绕回0桶继续遍历if bucket == bucketShift(it.B) {bucket = 0it.wrapped = true}i = 0}// 遍历桶内元素for ; i < bucketCnt; i++ {// 从offset槽开始offi := (i + it.offset) & (bucketCnt - 1)// 跳过空槽if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {// TODO: emptyRest is hard to use here, as we start iterating// in the middle of a bucket. It's feasible, just tricky.continue}// 获取元素key、valuek := 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() {// Special case: iterator was started during a grow to a larger size// and the grow is not done yet. We're working on a bucket whose// oldbucket has not been evacuated yet. Or at least, it wasn't// evacuated when we started the bucket. So we're iterating// through the oldbucket, skipping any keys that will go// to the other new bucket (each oldbucket expands to two// buckets during a grow).// 若key是有效的if t.reflexivekey() || t.key.equal(k, k) {// If the item in the oldbucket is not destined for// the current new bucket in the iteration, skip it.// 如果旧桶中的项在迭代中不打算用于当前的新桶,则跳过它。hash := t.hasher(k, uintptr(h.hash0))if hash&bucketMask(it.B) != checkBucket {continue}} else {// 对k!=k,也就是nil之类的,判断是否属于该新桶// 不是,则跳过// Hash isn't repeatable if k != k (NaNs). We need a// repeatable and randomish choice of which direction// to send NaNs during evacuation. We'll use the low// bit of tophash to decide which way NaNs go.// NOTE: this case is why we need two evacuate tophash// values, evacuatedX and evacuatedY, that differ in// their low bit.if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {continue}}}// 如果当前桶未扩容迁移,或者是每次hash不一致的key,获取到key、value添加到迭代器中if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||!(t.reflexivekey() || t.key.equal(k, k)) {// This is the golden data, we can return it.// OR// key!=key, so the entry can't be deleted or updated, so we can just return it.// That's lucky for us because when key!=key we can't look it up successfully.it.key = kif t.indirectelem() {e = *((*unsafe.Pointer)(e))}it.elem = e} else {// 数据已经迁移情况下,处理键已被删除、更新或删除并重新插入的情况,定位数据,最后添加遍历key、value// The hash table has grown since the iterator was started.// The golden data for this key is now somewhere else.// Check the current hash table for the data.// This code handles the case where the key// has been deleted, updated, or deleted and reinserted.// NOTE: we need to regrab the key as it has potentially been// updated to an equal() but not identical key (e.g. +0.0 vs -0.0).rk, re := mapaccessK(t, h, k)if rk == nil {continue // key has been deleted}it.key = rkit.elem = re}// 迭代器记录进度it.bucket = bucketif it.bptr != b { // avoid unnecessary write barrier; see issue 14921it.bptr = b}it.i = i + 1it.checkBucket = checkBucketreturn}// 遍历溢出桶b = b.overflow(t)i = 0goto next
}
通过以上代码分析,可以看出:
-
在扩容时遍历,
-
如果当前遍历的桶已经迁移好了,那么取新桶
-
如果仍然处于旧桶,则取旧桶。
但值得注意的是要过滤掉那些不属于该新桶的旧桶元素。因为旧桶在扩容迁移时会分为两块,当前指向的新桶只属于其中之一
-
-
bucket从初始桶逐渐递增,保证正常桶都能遍历到。此外也保证了完整遍历溢出桶,直到溢出桶为空
-
通过记录是否从头遍历的标志和起始bucket,以及在扩容过程中过滤不属于该新桶的元素来保证不会重复遍历
Ref
- https://zhuanlan.zhihu.com/p/597348765
- https://www.cnblogs.com/cnblogs-wangzhipeng/p/13292524.html
- https://qcrao.com/post/dive-into-go-map/
相关文章:
golang map真有那么随机吗?——map遍历研究
在随机选取map中元素时,本想用map遍历的方式来返回,但是却并没有通过测试。 那么难道map的遍历并不是那么的随机吗? 以下代码参考go1.18 hiter是map遍历的结构,主要记录了当前遍历的元素、开始位置等来完成整个遍历过程 // A ha…...
详细分析对比copliot和ChatGPT的差异
Copilot 和 ChatGPT 是两种不同的AI工具,分别在不同领域展现出了强大的功能和潜力: GitHub Copilot 定位与用途:GitHub Copilot 是由GitHub(现为微软子公司)和OpenAI合作开发的一款智能代码辅助工具。它主要集成于Visu…...
TENT:熵最小化的Fully Test-Time Adaption
摘要 在测试期间,模型必须自我调整以适应新的和不同的数据。在这种完全自适应测试时间的设置中,模型只有测试数据和它自己的参数。我们建议通过test entropy minimization (tent[1])来适应:我们通过其预测的熵来优化模型的置信度。我们的方法估计归一化…...
研发日记,Matlab/Simulink避坑指南(五)——CAN解包 DLC Bug
文章目录 前言 背景介绍 问题描述 分析排查 解决方案 总结 前言 见《研发日记,Matlab/Simulink避坑指南(一)——Data Store Memory模块执行时序Bug》 见《研发日记,Matlab/Simulink避坑指南(二)——非对称数据溢出Bug》 见《…...
机器人3D视觉引导半导体塑封上下料
半导体塑封上下料是封装工艺中的重要环节,直接影响到产品的质量和性能。而3D视觉引导技术的引入,使得这一过程更加高效、精准。它不仅提升了生产效率,减少了人工操作的误差,还为半导体封装技术的智能化升级奠定了坚实的基础。 传统…...
(十二)Head first design patterns代理模式(c++)
代理模式 代理模式:创建一个proxy对象,并为这个对象提供替身或者占位符以对这个对象进行控制。 典型例子:智能指针... 例子:比如说有一个talk接口,所有的people需要实现talk接口。但有些人有唱歌技能。不能在talk接…...
C++从零开始的打怪升级之路(day21)
这是关于一个普通双非本科大一学生的C的学习记录贴 在此前,我学了一点点C语言还有简单的数据结构,如果有小伙伴想和我一起学习的,可以私信我交流分享学习资料 那么开启正题 今天分享的是关于vector的题目 1.删除有序数组中的重复项 26. …...
《设计模式的艺术》笔记 - 观察者模式
介绍 观察者模式定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。 实现 myclass.h // // Created by yuwp on 2024/1/12. //#ifndef DESIGNPATTERNS_MYCLASS_H #define DESIGNPATTERNS_MYCLA…...
Java如何对OSS存储引擎的Bucket进行创建【OSS学习】
在前面学会了如何开通OSS,对OSS的一些基本操作,接下来记录一下如何通过Java代码通过SDK对OSS存储引擎里面的Bucket存储空间进行创建。 目录 1、先看看OSS: 2、代码编写: 3、运行效果: 1、先看看OSS: 此…...
ModuleNotFoundError: No module named ‘half_json‘
问题: ModuleNotFoundError: No module named ‘half_json’ 原因: 缺少jsonfixer包 解决方法: pip install jsonfixerjson修正包地址: https://github.com/half-pie/half-json...
深入探究 Android 内存泄漏检测原理及 LeakCanary 源码分析
深入探究 Android 内存泄漏检测原理及 LeakCanary 源码分析 一、什么是内存泄漏二、内存泄漏的常见原因三、我为什么要使用 LeakCanary四、LeakCanary介绍五、LeakCanary 的源码分析及其核心代码六、LeakCanary 使用示例 一、什么是内存泄漏 在基于 Java 的运行时中࿰…...
Linux CentOS使用Docker搭建laravel项目环境(实践案例详细说明)
一、安装docker # 1、更新系统软件包: sudo yum update# 2、安装Docker依赖包 sudo yum install -y yum-utils device-mapper-persistent-data lvm2# 3、添加Docker的yum源: sudo yum-config-manager --add-repo https://download.docker.com/linux/cen…...
第六课:Prompt
文章目录 第六课:Prompt1、学习总结:Prompt介绍预训练和微调模型回顾挑战 Pre-train, Prompt, PredictPrompting是什么?prompting流程prompt设计 课程ppt及代码地址 2、学习心得:3、经验分享:4、课程反馈:5、使用Mind…...
网络安全(初版,以后会不断更新)
1.网络安全常识及术语 资产 任何对组织业务具有价值的信息资产,包括计算机硬件、通信设施、IT 环境、数据库、软件、文档 资料、信息服务和人员等。 漏洞 上边提到的“永恒之蓝”就是windows系统的漏洞 漏洞又被称为脆弱性或弱点(Weakness)&a…...
开始学习Vue2(脚手架,组件化开发)
一、单页面应用程序 单页面应用程序(英文名:Single Page Application)简 称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的 一个 HTML 页面,所有的功能与交互都在这唯一的一个页面内完成。 二、vue-cli …...
平替heygen的开源音频克隆工具—OpenVoice
截止2024-1-26日,全球范围内语音唇形实现最佳的应该算是heygen,可惜不但要魔法,还需要银子;那么有没有可以平替的方案,答案是肯定的。 方案1: 采用国内星火大模型训练自己的声音,然后再用下面…...
【自动化测试】读写64位操作系统的注册表
自动化测试经常需要修改注册表 很多系统的设置(比如:IE的设置)都是存在注册表中。 桌面应用程序的设置也是存在注册表中。 所以做自动化测试的时候,经常需要去修改注册表 Windows注册表简介 注册表编辑器在 C:\Windows\regedit…...
php二次开发股票系统代码:腾讯股票数据接口地址、批量获取股票信息、转换为腾讯接口指定的股票格式
1、腾讯股票数据控制器 <?php namespace app\index\controller;use think\Model; use think\Db;const BASE_URL http://aaaaaa.aaaaa.com; //腾讯数据地址class TencentStocks extends Home { //里面具体的方法 }2、请求接口返回内容 function juhecurl($url, $params f…...
uniapp 在static/index.html中添加全局样式
前言 略 在static/index.html中添加全局样式 <style>div {background-color: #ccc;} </style>static/index.html源码: <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><meta http-…...
acrobat调整pdf的页码和实际页码保持一致
Acrobat版本 具体操作 现在拿到pdf的结构如下: pdf页码实际页码1-10页无页码数11页第1页 操作,选择pdf第10页,右键点击 具体设置 最终效果...
OpenClaw近一月版本更替讲解
如果你最近没追 OpenClaw 的更新,最容易产生一种错觉:它是不是又只是多接了几个模型、多加了几个花哨功能? 我看完最近一个月的变化后,感觉不是这样。 OpenClaw 这一个月真正值得关注的地方,不是“它更炫了”ÿ…...
开源与闭源软件质量对比:工程实践与激励机制才是关键
1. 开源与闭源软件质量之争:一场被误解的辩论最近和几位同行聊起软件质量的话题,不出所料,讨论很快又滑向了那个经典的对立:开源软件和闭源(或称专有)软件,到底谁的质量更好?场面一度…...
金融文档实时检索难?电商SKU模糊匹配慢?DeepSeek垂直搜索3类高价值场景落地,附可复用Prompt工程模板
更多请点击: https://intelliparadigm.com 第一章:金融文档实时检索难?电商SKU模糊匹配慢?DeepSeek垂直搜索3类高价值场景落地,附可复用Prompt工程模板 三大典型业务痛点与DeepSeek-R1适配逻辑 传统向量检索在专业领…...
基于PanoSim5.0虚拟仿真平台的自主代客泊车AVP系统开发教程
1. PanoSim5.0与AVP系统开发入门指南 第一次接触PanoSim5.0时,我和大多数开发者一样被它丰富的功能模块震撼到了。这个国产仿真平台不仅支持高精度的车辆动力学建模,还能实现逼真的传感器仿真和环境渲染。对于自主代客泊车(AVP)这种需要反复测试的场景来…...
用ChatGPT批量生成高互动Instagram内容:5步工作流+4类避坑红线(数据实测CTR提升217%)
更多请点击: https://intelliparadigm.com 第一章:用ChatGPT批量生成高互动Instagram内容:5步工作流4类避坑红线(数据实测CTR提升217%) 借助ChatGPT API 与 Instagram Graph API 的协同调度,可构建轻量级自…...
AI智能体文化档案:用Next.js静态站点构建数字人类学观察站
1. 项目概述:一个观察AI智能体文化的数字档案馆最近在GitHub上闲逛,发现了一个让我眼前一亮的项目:The MoltStein Files。这可不是一个普通的代码仓库,而是一个专注于记录和存档AI智能体之间“社交”行为的数字档案馆。简单来说&a…...
DeepSeek总结的pg_clickhouse v0.3.0的新特性
来源:https://justatheory.com/2026/05/pg_clickhouse-0.3.0/ pg_clickhouse 的新特性 日期: 2026年5月11日 关于 pg_clickhouse 项目的新闻汇总。 新特性 首先,几周前 ClickHouse 博客发表了《pg_clickhouse 的新特性》一文,其中我介绍了该扩…...
Git Conflict Resolution
1. 这篇文章解决什么问题? Git 冲突不是异常情况,而是多人协作和分支开发里的正常现象。 常见问题包括: 1. 为什么会产生冲突? 2. 冲突文件里的 <<<<<<<、、>>>>>>> 是什么?…...
基于Godot与Roslyn构建现代化.NET IDE:SharpIDE架构解析与实践
1. 项目概述:一个为.NET开发者打造的现代IDE如果你是一个.NET开发者,尤其是长期使用C#进行开发,那么你肯定对Visual Studio和Visual Studio Code这两款工具又爱又恨。Visual Studio功能强大但略显笨重,VS Code轻快但针对.NET的原生…...
在Node.js后端服务中集成Taotoken调用大模型指南
🚀 告别海外账号与网络限制!稳定直连全球优质大模型,限时半价接入中。 👉 点击领取海量免费额度 在Node.js后端服务中集成Taotoken调用大模型指南 将大模型能力集成到后端服务是现代应用开发的常见需求。Taotoken平台提供了OpenA…...
