Go中MAP底层原理分析

MAP底层原理分析

参考

  1. https://golang.design/go-questions/map/principal
  2. map | Golang 中文学习文档

  1. 先来看一下map结构体,(runtime.hmap结构体就是代表着 go 中的map,与切片一样map的内部实现也是结构体)

    type hmap struct {// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.// Make sure this stays in sync with the compiler's definition.count     int // # live cells == size of map.  Must be first (used by len() builtin)flags     uint8B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for detailshash0     uint32 // hash seedbuckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growingnevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)clearSeq   uint64extra *mapextra // optional fields
    }
    

    内部实现图

在这里插入图片描述

简单说明一下各个字段含义

  • count:表示 hamp 中的元素数量,结果等同于len(map)

  • flags : hmap 的标志位,用于表示 hmap 处于什么状态,有以下几种可能

    const (iterator     = 1 // 迭代器正在使用桶oldIterator  = 2 // 迭代器正在使用旧桶hashWriting  = 4 // 一个协程正在写入hmapsameSizeGrow = 8 // 正在等量扩容
    )
    
  • B:hmap 中的哈希桶的数量为1 << B

  • noverflow,hmap 中溢出桶的大致数量。

  • 哈希种子,在 hmap 被创建时指定,用于计算哈希值。

  • buckets,存放哈希桶数组的指针。

  • oldbuckets,存放 hmap 在扩容前哈希桶数组的指针。

  • extra,存放着 hmap 中的溢出桶,溢出桶指的是就是当前桶已经满了,创建新的桶来存放元素,新创建的桶就是溢出桶。

    type mapextra struct {// 溢出桶的指针切片overflow    *[]*bmap// 扩容前旧的溢出桶的指针切片oldoverflow *[]*bmap// 指向下一个空闲的溢出桶的指针nextOverflow *bmap
    }
    
  1. 一直说的桶是个什么东西 ?

    hamp 中的buckets也就是桶切片指针,在 go 中对应的结构为runtime.bmap,如下所示

    // A bucket for a Go map.
    type bmap struct {tophash [abi.OldMapBucketCount]uint8
    }
    // OldMapBucketCount是如下的这个变量
    // Maximum number of key/elem pairs a bucket can hold.
    OldMapBucketCountBits = 3 // log2 of number of elements in a bucket.
    OldMapBucketCount     = 1 << OldMapBucketCountBits // 默认为8
    

    表面上看,它只有一个字段tophash,不过实际上来说,bmap的字段不止这些,这是因为map可以存储各种类型的键值对,所以需要在编译时根据类型来推导占用的内存空间,在cmd/compile/internal/reflectdata/reflect.go中的MapBucketType函数的功能就是在编译时构造 bmap,它会进行一系列检查工作,比如 key 的类型是否comparable

    // MapBucketType makes the map bucket type given the type of the map.
    func MapBucketType(t *types.Type) *types.Type
    

    bmap内部结构:HOB Hash 指的就是 top hash。

在这里插入图片描述

实际上bmap结构如下;不过这些字段对我们是不可见的,go 在实际操作中是通过移动 unsafe 指针来进行访问

// 实际运行时定义(简化为可理解的结构)
type bmap struct {// 哈希值高8位数组 (每个槽位1字节)tophash [abi.OldMapBucketCount]uint8  // bucketCnt = 8// 以下是隐式字段(编译器在编译时根据键值类型动态生成内存布局)keys    [abi.OldMapBucketCount]keyType   // 键数组(连续存储)elems   [abi.OldMapBucketCount]elemType  // 值数组(连续存储)注意:1.17+ 改名为 elems// 溢出桶指针(重要:在 keys/elems 之后存储)overflow *bmap
}
// 注意:有些文章还有pad字段 ,该字段是为了保持内存对齐,提高一些操作的效率的

tophash字段是什么 ?

首先,根据字面意思就是一个为uint8类型的长度为 8的数组 ,此时也就定义了 一个桶中可以存放8个键值对,桶中每个元素是uint8,代表每个键(key)对应的hash值得高八位, 用于定位该键(key)在本桶的位置。

另外,tophash还有一些特殊的值,通常是处于扩容的迁移状态下:

const (emptyRest      = 0 // 当前元素是空的,并且该元素后面也没有可用的键值了emptyOne       = 1 // 当前元素是空的,但是该元素后面有可用的键值。evacuatedX     = 2 // 扩容时出现,只能出现在oldbuckets中,表示当前元素被搬迁到了新哈希桶数组的上半区evacuatedY     = 3 // 扩容时出现只能出现在oldbuckets中,表示当前元素被搬迁到了新哈希桶数组的下半区evacuatedEmpty = 4 // 扩容时出现,元素本身就是空的,在搬迁时被标记minTopHash     = 5 // 对于一个正常的键值来说tophash的最小值
)
  • minTopHash:当一个 cell(每个tophash元素抽象成每一个cell) 的 tophash 值小于 minTopHash 时,标志这个 cell 的迁移状态。因为这个状态值是放在 tophash 数组里,为了和正常的哈希值区分开,会给 key 计算出来的哈希值一个增量:minTopHash。这样就能区分正常的 top hash 值和表示状态的哈希值
  • 其余变量是迁移过程中用到的 ,后续说明
  1. key定位过程

    参考文章中说明的很好:


    key 经过哈希计算后得到哈希值,共 64 个 bit 位(64位机,32位机就不讨论了,现在主流都是64位机),计算它到底要落在哪个桶时,只会用到最后 B 个 bit 位。还记得前面提到过的 B 吗?如果 B = 5,那么桶的数量,也就是 buckets 数组的长度是 2^5 = 32。

    例如,现在有一个 key 经过哈希函数计算后,得到的哈希结果是:

     10010111 | 000011110110110010001111001010100010010110010101010 │ 01010
    

    用最后的 5 个 bit 位,也就是 01010,值为 10,也就是 10 号桶。这个操作实际上就是取余操作,但是取余开销太大,所以代码实现上用的位操作代替。

    再用哈希值的高 8 位,找到此 key 在 bucket 中的位置,这是在寻找已有的 key。最开始桶内还没有 key,新加入的 key 会找到第一个空位,放入。

在这里插入图片描述

上图中,假定 B = 5,所以 bucket 总数就是 2^5 = 32。首先计算出待查找 key 的哈希,使用低 5 位 00110,找到对应的 6 号 bucket,使用高 8 位 10010111,对应十进制 151,在 6 号 bucket 中寻找 tophash 值(HOB hash)为 151 的 key,找到了 2 号槽位,这样整个查找过程就结束了。

如果在 bucket 中没找到,并且 overflow 不为空,还要继续去 overflow bucket 中寻找,直到找到或是所有的 key 槽位都找遍了,包括所有的 overflow bucket。


keyvalue的定位方法

// key 定位公式
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))// value 定位公式
e := add(unsafe.Pointer(b), dataOffset+abi.OldMapBucketCount*uintptr(t.KeySize)+i*uintptr(t.ValueSize))

b 是 bmap 的地址,这里 bmap 还是源码里定义的结构体,只包含一个 tophash 数组,经编译器扩充之后的结构体才包含 key,value,overflow 这些字段。dataOffset 是 key 相对于 bmap 起始地址的偏移, 也就是bmap中key地址开始的位置:

dataOffset = unsafe.Offsetof(struct {b bmapv int64}{}.v)

当定位到一个具体的 bucket 时,里层循环就是遍历这个 bucket 里所有的 cell,或者说所有的槽位,也就是 bucketCnt=8 个槽位。整个循环过程:

在这里插入图片描述

遍历过程中会判断每一个cell的状态 ,判断这个bucket是否搬迁完毕,源码中用到的函数

func evacuated(b *bmap) bool {h := b.tophash[0]return h > empty && h < minTopHash
}

只取了 tophash 数组的第一个值,判断它是否在 0-4 之间。对比上面的常量,当 top hash 是 evacuatedEmptyevacuatedXevacuatedY 这三个值之一,说明此 bucket 中的 key 全部被搬迁到了新 bucket。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82559.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

#开发环境篇:postMan可以正常调通,但是浏览器里面一直报403

本地header代理下面内容即可 headers: { // 添加必要的请求头 ‘Host’: ‘服务端域名’, ‘Origin’: https://服务端域名, ‘Referer’: https://服务端域名 }, devServer: {// 本地开发代理API地址proxy: {^/file: {target: https://服务端域名,changeOrigin: true, // 是否…

【论文阅读 | PR 2024 |ICAFusion:迭代交叉注意力引导的多光谱目标检测特征融合】

论文阅读 | PR 2024 |ICAFusion&#xff1a;迭代交叉注意力引导的多光谱目标检测特征融合 1.摘要&&引言2.方法2.1 架构2.2 双模态特征融合&#xff08;DMFF&#xff09;2.2.1 跨模态特征增强&#xff08;CFE&#xff09;2.2.2 空间特征压缩&#xff08;SFS&#xff09;…

效率、便捷、安全:智慧充电桩一站式解决方案如何重塑新能源充电体验?

在新能源浪潮席卷全球的背景下&#xff0c;电动汽车的普及对充电基础设施提出了更高要求。传统充电模式因效率低、操作繁琐、安全隐患等问题&#xff0c;难以满足用户需求。智慧充电桩一站式解决方案应运而生&#xff0c;通过技术创新将效率、便捷与安全融为一体&#xff0c;彻…

杰发科技AC7840——Timer修改重装载值

需要在运行过程中修改定时器的中断时间 int main(void) {SystemClock_Config(); /*时钟初始化*/GPIO_LedInit(); /*GPIO初始化*/TIMER_Init(); /*定时器初始化*/InitDebug(); …

https和http有什么区别-http各个版本有什么区别

http和 https的区别 HTTP&#xff08;超文本传输协议&#xff09;和 HTTPS&#xff08;安全超文本传输协议&#xff09;是两种用于在网络上传输数据的协议&#xff0c;它们的主要区别在于安全性&#xff1a; HTTP&#xff08;Hypertext Transfer Protocol&#xff09;&#x…

低秩矩阵、奇异值矩阵和正交矩阵

低秩矩阵 低秩矩阵&#xff08;Low-rank Matrix&#xff09;是指秩&#xff08;rank&#xff09;远小于其行数和列数的矩阵&#xff0c;即 r a n k ( M ) r ≪ min ⁡ ( m , n ) rank(M) r \ll \min(m,n) rank(M)r≪min(m,n)。其核心特点是信息冗余性&#xff0c;可通过少量…

对抗性提示:大型语言模型的安全性测试

随着大语言模型&#xff08;LLM&#xff09;在虚拟助手、企业平台等现实场景中的深度应用&#xff0c;其智能化与响应速度不断提升。然而能力增长的同时&#xff0c;风险也在加剧。对抗性提示已成为AI安全领域的核心挑战&#xff0c;它揭示了即使最先进的模型也可能被操纵生成有…

SSM 框架核心知识详解(Spring + SpringMVC + MyBatis)

&#x1f331; 第一部分&#xff1a;Spring 核心原理与使用 1. 什么是 Spring Spring 是一个开源的 Java 企业级开发框架&#xff0c;旨在简化 Java 企业应用程序开发。它核心思想是控制反转&#xff08;IoC&#xff09;和面向切面编程&#xff08;AOP&#xff09;&#xff0…

基于 Alpine 定制单功能用途(kiosk)电脑

前言 故事回到 7 年前, 在网上冲浪的时候发现了一篇介绍使用 Ubuntu 打造 kiosk 单功能用途电脑的文章, 挺好玩的, 就翻译了一下并比葫芦画瓢先后用了 CentOS 7, ArchLinux 进行了实现. 历史文章: 翻译 - 使用Ubutnu14.04和Chrome打造单功能用途电脑(大屏展示电脑) 使用CentOS…

【机器学习及深度学习】机器学习模型的误差:偏差、方差及噪声

机器学习模型的误差分析 V1.0机器学习模型的衡量准则概念引入机器学习模型误差分析误差出现的原因及消除 V1.0 机器学习模型的衡量准则 衡量机器学习模型的好坏可以考虑以下几个方面&#xff1a; 偏差&#xff08;Bias&#xff09;&#xff1a; 在充分训练的情况下&#xff0…

混沌映射(Chaotic Map)

一.定义 混沌映射是指一类具有混沌行为的离散时间非线性动力系统&#xff0c;通常由递推公式定义。其数学形式为 &#xff0c;其中 f 是非线性函数&#xff0c;θ 为参数。它们以简单的数学规则生成复杂的、看似随机的轨迹&#xff0c;是非线性动力学和混沌理论的重要研究对象…

多群组部署

相关概念 星形拓扑和并行多组 如下图&#xff0c;星形组网拓扑和并行多组组网拓扑是区块链应用中使用较广泛的两种组网方式。 星形拓扑&#xff1a;中心机构节点同时属于多个群组&#xff0c;运行多家机构应用&#xff0c;其他每家机构属于不同群组&#xff0c;运行各自应用…

基于vue3-elemenyui的动态列案例

本案例主要是实现数据模型的解析以及实现el-table的动态列加载。 1.数据结构 公司A\B\C\测试1&#xff0c;是列&#xff0c;功能-url&#xff0c;是行数据&#xff0c;其中功能x是行头。 this.rawData [{companyName: "公司A",rpWebShows: [{ "功能1": &…

Kerberos面试内容整理-Kerberos 与 LDAP/Active Directory 的集成

Kerberos 通常不会单独存在于企业环境中,而是与目录服务相结合以提供完整的身份管理方案。其中,Active Directory (AD) 是 Kerberos 集成应用的典型代表。Active Directory 是微软的目录服务,实现了 LDAP(轻量级目录访问协议)目录和 Kerberos 认证的融合。在 AD 域控制器上…

Oracle DG库控制文件IO错误导致宕机的应急处理

Oracle DG库控制文件IO错误导致宕机的应急处理 事故现场偷天换日棋差一招事故现场 一套Oracle 19c DG环境的备库宕机。 根据告警时间检查实例宕机时间点附近的alert日志有如下重要信息: 2025-05-25T23:34:10.705385+08:00 KCF: read, write or open error, block=0x3377ee …

《前端面试题:前端盒模型》

前端盒模型完全指南&#xff1a;从原理到面试实战 &#x1f381; 端午快乐&#xff01; 各位前端小伙伴&#xff0c;端午节快乐&#xff01;&#x1f96e; 在这个粽叶飘香的时节&#xff0c;愿你的代码如龙舟般一往无前&#xff0c;bug 如咸蛋黄般被完美包裹&#xff01;今天我…

BERT:让AI真正“读懂”语言的革命

BERT&#xff1a;让AI真正“读懂”语言的革命 ——图解谷歌神作《BERT: Pre-training of Deep Bidirectional Transformers》 2018年&#xff0c;谷歌AI团队扔出一篇核弹级论文&#xff0c;引爆了整个NLP领域。这个叫BERT的模型在11项任务中屠榜&#xff0c;甚至超越人类表现…

爬虫入门:从基础到实战全攻略

&#x1f9e0; 一、爬虫基础概念 1.1 爬虫定义 爬虫&#xff08;Web Crawler&#xff09;是模拟浏览器行为&#xff0c;自动向服务器发送请求并获取响应数据的一种程序。主要用于从网页中提取结构化数据&#xff0c;供后续分析、展示或存储使用。 1.2 爬虫特点 数据碎片化&…

uni-app学习笔记二十一--pages.json中tabBar设置底部菜单项和图标

如果应用是一个多 tab 应用&#xff0c;可以通过 tabBar 配置项指定一级导航栏&#xff0c;以及 tab 切换时显示的对应页。 在 pages.json 中提供 tabBar 配置&#xff0c;不仅仅是为了方便快速开发导航&#xff0c;更重要的是在App和小程序端提升性能。在这两个平台&#xff…

行业分析---小米汽车2025第一季度财报

1 背景 最近几年是新能源汽车的淘汰赛&#xff0c;前短时间比亚迪再次开始了降价&#xff0c;导致一片上市车企的股价大跌&#xff0c;足见车圈现在的敏感度。因此笔者会一直跟踪新势力车企的财报状况&#xff0c;对之前财报分析感兴趣的读者朋友可以参考以下博客&#xff1a;…