Go Map 源码分析(一)

news2025/1/20 8:11:51

Go语言中的map是通过哈希表实现的,其底层结构和实现机制如下:

一、hash 结构

hmap结构体:是map的头部结构,主要字段及含义如下:

  • count:表示当前哈希表中的元素数量,与len()函数相对应。
  • flags:标记字段,用于标记是否正在进行读写操作,以便实现并发读写的检测。 所以它不是并发安全的
  • B:表示当前哈希表持有的buckets数量的对数,即len(buckets) == 2^B。
  • noverflow:溢出桶的大致数量。
  • hash0:hash种子。
  • buckets:存储2^B个桶的数组,是一个unsafe.Pointer,因为Go语言中支持不同类型的键值对,需要在编译时才能确定map的类型。
  • oldbuckets:扩容时用于保存之前的buckets的字段,大小是buckets的一半。
  • nevacuate:迁移进度计数器,记录buckets中小于该值的bucket已经完成迁移。
  • extra:指向mapextra结构体的指针,用于存储一些可选字段。
type hmap struct {
	// 元素个数,调用 len(map) 时,直接返回此值 
	count int
	flags uint8
	// buckets 的对数 log_2
	B uint8
	// overflow 的 bucket 近似数
	noverflow uint16
	// 计算 key 的哈希的时候会传入哈希函数
	hash0 uint32
	// 指向 buckets 数组,大小为 2^B
	// 如果元素个数为 0,就为 nil
	buckets unsafe.Pointer
	// 扩容的时候,buckets 长度会是 oldbuckets 的两倍 
	oldbuckets unsafe.Pointer
	// 指示扩容进度,小于此地址的 buckets 完成迁移 
	nevacuate uintptr
	extra *mapextra
}

bmap结构体:是哈希表中的桶,每个bmap能够存储8个键值对,并且设有一个指针,当某个bmap存满时,就会申请新的bmap进行存储,并与前一个bmap构成链表。其结构如下:

  • tophash:数组,用于存储每个key hash之后的高位hash值。
  • keys:数组,用于存储key。
  • elems:数组,用于存储value。
  • overflow:溢出指针,指向下一个bmap的地址。

下面是map 的初始形态,

type bmap struct {
	tophash [bucketCnt]uint8
}

但是编译器会对go 的map 给塞几个字段

type bmap struct { 
	topbits [8]uint8
	keys [8]keytype
	values  [8]valuetype
	pad uintptr
	overflow uintptr
}

当 map 的 key 和 value 都不是指针,并且 size 都小于 128 字节的情况下,会把 bmap 标
记为不含指针,这样可以避免 GC 时扫描整个 hmap,提升效率。

但是map 中是包含了一个 overflow 字段的, 这个字段是指针类型的, 这个时候我们可能会把相应的值移动到extra *mapextra 这个字段上来, 并且会开启maxarea的两个字段启用 overflow 和 oldoverflow 字段。

mapextra结构体:主要字段如下:

  • overflow:指向当前buckets的溢出桶数组的指针。
  • oldoverflow:指向oldbuckets的溢出桶数组的指针。
  • nextOverflow:指向还未使用的、提前分配的溢出桶链表。

二、哈希

map 的一个关键点在于哈希函数的选择。在程序启动时,Go 会检测 CPU 是否支持 aes,如果支持, 则使用 aes hash,否则使用 memhash。这在函数 alginit()中完成,源码位于路径 src/runtime/alg.go 下。对于 hash 函数,有加密型和非加密型。加密型的一般用于加密数据、数字摘要等,典型代表 就是 md5、sha1、sha256、aes256 这类;非加密型的一般就是查找,如 MurmurHash 等

Go语言中map的哈希函数会根据键的类型和值来计算一个哈希值,这个哈希值是一个32位或64位的整数。哈希函数的设计目标是尽量使不同的键映射到不同的哈希值,以减少哈希冲突。对于不同的键类型,Go语言会采用不同的哈希算法,例如对于字符串键,会根据字符串的内容计算哈希值;对于整数键,则直接使用整数本身或其某种变换作为哈希值。

但计算它到底要落在哪个 bucket 时,只会用到最后 B 个 bit 位。如果 B = 5,那么桶的数量,也就是 buckets 数组的长度是 2^5 = 32。例如,现在有一个 key 经过哈希函数计算后,得到的哈希结果是:

10010111 | 000011110110110010001111001010100010010110010101010 | 00110
  1. 首先会根据后面的5 位去定位到对应的桶的位置, 这里表示第6号桶
  2. 然后取hash 值的高8 位, 找到key 在桶里面的位置

因为根据后 B 个 bit 位决定 key 落入的 bucket 编号,也就是桶编号,因此肯定会存在冲突。当两个不同的 key 落在同一个桶中,也就是发生了哈希冲突。冲突的解决手段是用链表法:在 bucket 中,从前往后找到第一个空位,放入新加入的有冲突的 key。之后,在查找某个 key 时,
先找到对应的桶,再去遍历 bucket 中所有的 key

key, value的内存布局其实也是比较有意思的, 它的<key, value> 不是放在一起的:

在这里插入图片描述

如果是map 的 assign 的过程情况, 可能又会存在不同, 因为赋值操作可能存在两种情况

  • 插入操作: 当前的key 不存在, 我们需要插入
  • 修改操作, 当前的key 存在, 我们直接修改即可

三、赋值和删除

  1. map 的赋值操作:
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer

整体而言,map的赋值流程十分简洁。首先对key计算hash值,依据该值按照既定流程确定赋值位置,可能是插入新key,也可能是更新旧key,然后在相应位置执行赋值操作。源码与前面key定位过程大致相同,核心在于一个双层循环:外层循环遍历bucket及overflow bucket,内层循环遍历单个bucket的所有槽位,因篇幅有限,此处不再展开代码注释。

在赋值过程中,有几点关键之处。mapassign函数会先检查map的标志位flags,若flags的写标志位被置为1,意味着有其他协程正在进行写操作,而assign同样是写操作,这将导致并发写冲突,从而使程序直接panic,这也表明map不具备协程安全性。

map的扩容采用渐进式方式。当 map 处于扩容阶段,定位 key 到某个 bucket 后,需确保该 bucket 对应的老 bucket 已完成迁移,即老 bucket 中的 key 都已迁移到新 bucket(老bucket中的key会被分散到两个新bucket),之后才能在新bucket中进行插入或更新操作。只有完成迁移,才能安全地在新bucket里确定key的安置地址,进而进行后续赋值操作。

接下来是定位 key 放置位置的关键步骤:准备两个指针,inserti 指向 key 的 hash 值在 tophash 数组的位置,insertk 指向 cell 的位置,即 key 最终放置的地址。而对应value的位置则容易计算,tophash数组中的索引位置决定了key在整个bucket中的位置(共8个key),value的位置需“跨过”8个key的长度。

如果当前bucket的8个key已经全部占用,跳出循环后会发现inserti和insertk指针均为空。这意味着需要在bucket后面挂载一个overflow bucket,甚至可能是在已有的overflow bucket后面再挂载一个。这表明有大量key被哈希到了同一个bucket。在这种情况下,在正式放置key之前,还需要检查map的状态,判断是否需要扩容。如果满足扩容条件,则会主动触发一次扩容操作。

扩容完成之后,之前的查找定位 key 的过程,还得再重新走一次。因为扩容之后,key 的分 布发生了变化。

最后,会更新 map 相关的值,如果是插入新 key,map 的元素数量字段 count 值会加 1;并 且会将 hashWriting 写标志位清零。

  1. map 的删除操作:
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer)

当然,只需关心 mapdelete 函数。它首先会检查 h.flags 标志,如果发现写标志位是 1,直接 panic,因为这表明有其他协程同时在进行写操作。大致的逻辑如下:

  1. 检测是否存在并发写操作。
  2. 计算 key 的哈希,找到落入的 bucket。
  3. 设置写标志位。
  4. 检查此 map 是否正在扩容的过程中,如果是则直接触发一次搬迁操作。
  5. 两层循环,核心是找到 key 的具体位置。寻找过程都是类似的,在 bucket 中挨个 cell 寻找。
  6. 找到对应位置后,对 key 或者 value 进行“清零”操作。
  7. 将 count 值减 1,将对应位置的 tophash 值置成 emptyOne。
  8. 最后,检测此槽位后面是否都是空,若是将 tophash 改成 emptyRest。
  9. 若前一步成功,则继续向前扩大战果:将此 cell 之前的 tophash 值为 emptyOne 的槽位都
    置成 emptyRest。

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

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

相关文章

【大数据2025】Yarn 总结

分布式资源管理系统讲解总结 一、引言 围绕分布式资源管理系统展开&#xff0c;重点涵盖 Yarn 的简介、原理、资源调度策略以及运维和管理&#xff0c;旨在让学员全面掌握相关知识。Yet Another Resource Negotiator 二、Yarn 诞生背景 在 Hadoop 1.X 中仅有 HDFS 和 MapRe…

【AI日记】25.01.19

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 AI kaggle 比赛&#xff1a;Forecasting Sticker Sales 读书 书名&#xff1a;自由宪章阅读原因&#xff1a;作者哈耶克&#xff0c;诺贝尔经济学奖得主&#xff0c;之前读过他的 《通往奴役…

5.最长回文子串--力扣

给你一个字符串 s&#xff0c;找到 s 中最长的 回文子串。 示例 1&#xff1a; 输入&#xff1a;s “babad” 输出&#xff1a;“bab” 解释&#xff1a;“aba” 同样是符合题意的答案。 示例 2&#xff1a; 输入&#xff1a;s “cbbd” 输出&#xff1a;“bb” 原题如上&…

GCPAAS/DashBoard:完全免费的仪表盘设计,基于Vue+ElementUI+G2Plot+Echarts,开源代码,简单易用!还在等什么呢

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 GCPAAS/DashBoard&#xff0c;一款基于SpringBoot、MyBatisPlus、ElementUI、G2Plot、Echarts等技术栈的仪表盘设计器&#xff0c;具备仪表盘目录管理…

Linux——线程条件变量(同步)

Linux——多线程的控制-CSDN博客 文章目录 目录 文章目录 前言 一、条件变量是什么&#xff1f; 1、死锁的必要条件 1. 互斥条件&#xff08;Mutual Exclusion&#xff09; 2. 请求和保持条件&#xff08;Hold and Wait&#xff09; 3. 不可剥夺条件&#xff08;No Preemption&…

“AI 辅助决策系统:决策路上的智慧领航员

在当今瞬息万变的时代&#xff0c;无论是企业的运营管理&#xff0c;还是个人在生活中的重大抉择&#xff0c;都需要精准、高效的决策。然而&#xff0c;信息的繁杂和未来的不确定性&#xff0c;常常让决策变得困难重重。这时&#xff0c;AI 辅助决策系统宛如一位智慧的领航员&…

某讯一面,感觉问Redis的难度不是很大

前不久&#xff0c;有位朋友去某讯面试&#xff0c;他说被问到了很多关于 Redis 的问题&#xff0c;比如为什么用 Redis 作为 MySQL 的缓存&#xff1f;Redis 中大量 key 集中过期怎么办&#xff1f;如何保证缓存和数据库数据的一致性&#xff1f;我将它们整理出来&#xff0c;…

DDD - 微服务落地的技术实践

文章目录 Pre概述如何发挥微服务的优势怎样提供微服务接口原则微服务的拆分与防腐层的设计 去中心化的数据管理数据关联查询的难题Case 1Case 2Case 3 总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服…

闪豆多平台视频批量下载器

1. 视频链接获取与解析 首先&#xff0c;在哔哩哔哩网页中随意点击一个视频&#xff0c;比如你最近迷上了一个UP主的美食制作视频&#xff0c;想要下载下来慢慢学。点击视频后&#xff0c;复制视频页面的链接。复制完成后&#xff0c;不要急着关闭浏览器&#xff0c;因为接下来…

【STM32-学习笔记-14-】FLASH闪存

文章目录 FALSH闪存一、FLASH简介二、FLASH基本结构三、FLASH解锁四、使用指针访问存储器五、FLASH擦除以及编程流程Ⅰ、程序存储器全擦除1. 读取FLASH_CR的LOCK位2. 检查LOCK位是否为13. 设置FLASH_CR的MER 1和STRT 1&#xff08;如果LOCK位0&#xff09;4. 检查FLASH_SR的B…

微信消息群发(定时群发)-UI自动化产品(基于.Net平台+C#)

整理 | 小耕家的喵大仙 出品 | CSDN&#xff08;ID&#xff1a;lichao19897314&#xff09; 关联源码及工具下载https://download.csdn.net/download/lichao19897314/90096681https://download.csdn.net/download/lichao19897314/90096681https://download.csdn.net/download/…

FPGA产业全景扫描

随着芯片种类日益丰富、功能日益强大&#xff0c;人们不禁好奇&#xff1a;一块FPGA是如何从最初的概念一步步呈现在我们面前的&#xff1f; FPGA设计、FPGA原型验证/仿真、FPGA板级调试和应用&#xff0c;是FPGA从概念到应用的必经之路。本文将围绕这几个核心环节&#xff0c…

SW - 钣金零件保存成DWG时,需要将折弯线去掉

文章目录 SW - 钣金零件保存成DWG时&#xff0c;需要将折弯线去掉概述笔记备注END SW - 钣金零件保存成DWG时&#xff0c;需要将折弯线去掉 概述 如果做需要弯折的切割件&#xff0c;最好做成钣金零件。 最近做了几个小钣金(将钣金展开&#xff0c;建立新草图&#xff0c;在2…

git系列之revert回滚

1. Git 使用cherry-pick“摘樱桃” step 1&#xff1a; 本地切到远程分支&#xff0c;对齐要对齐的base分支&#xff0c;举例子 localmap git pull git reset --hard localmap 对应的commit idstep 2&#xff1a; 执行cherry-pick命令 git cherry-pick abc123这样就会将远程…

Excel重新踩坑6:工作实战总结之根据筛选条件求平均成绩

一、前言&#xff1a; 这个博客的实战场景&#xff1a;给了一组学生数据&#xff0c;这些数据中&#xff0c;有全市20个社区&#xff0c;1-9年级的学生各科成绩。要求按照各社区统计1-9年级的所有学生各科平均值。下面首先介绍会用到的一些函数&#xff0c;然后再简单说明实战…

【数据分析】02- A/B 测试:玩转假设检验、t 检验与卡方检验

一、背景&#xff1a;当“审判”成为科学 1.1 虚拟场景——法庭审判 想象这样一个场景&#xff1a;有一天&#xff0c;你在王国里担任“首席审判官”。你面前站着一位嫌疑人&#xff0c;有人指控他说“偷了国王珍贵的金冠”。但究竟是他干的&#xff0c;还是他是被冤枉的&…

SpringMVC 实战指南:打造高效 Web 应用的秘籍

第一章&#xff1a;三层架构和MVC 三层架构&#xff1a; 开发服务器端&#xff0c;一般基于两种形式&#xff0c;一种 C/S 架构程序&#xff0c;一种 B/S 架构程序使用 Java 语言基本上都是开发 B/S 架构的程序&#xff0c;B/S 架构又分成了三层架构三层架构&#xff1a; 表现…

通过idea创建的springmvc工程需要的配置

在创建的spring mvc工程中&#xff0c;使用idea开发之前需要配置文件包括porm.xml、web.xml、springmvc.xml 1、porm.xml 工程以来的spring库&#xff0c;主要包括spring-aop、spring-web、spring-webmvc&#xff0c;示例配置如下&#xff1a; <project xmlns"http:/…

二、点灯基础实验

嵌入式基础实验第一个就是点灯&#xff0c;地位相当于编程界的hello world。 如下为LED原理图&#xff0c;要让相应LED发光&#xff0c;需要给I/O口设置输出引脚&#xff0c;低电平&#xff0c;二极管才会导通 2.1 打开初始工程&#xff0c;编写代码 以下会实现BLINKY常亮&…

搭建一个基于Spring Boot的数码分享网站

搭建一个基于Spring Boot的数码分享网站可以涵盖多个功能模块&#xff0c;例如用户管理、数码产品分享、评论、点赞、收藏、搜索等。以下是一个简化的步骤指南&#xff0c;帮助你快速搭建一个基础的数码分享平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个Spring …