map 源码思考

news2024/11/27 23:36:49

go 语言中 map 比较的说,和 slice 有什么区别?如果 map 是从 int 到 int 类型的话,差别还真不大。map 的 key 退化为 slice 的下标,而 value 退化为 slice 的值。

但这样也存在很大的弊端:空间浪费严重。假设 map 中只有 2 个元素,key 值分别是0、1000,用 slice 表示的话,[1, 999] 的空间都被浪费掉了。

按照 slice 的思维,继续把 map 推广到 string 到 string 的类型,将 map 中 key 表示的 string 进行 hash 函数处理为数值类型,作为 slice 的下标。但运行 hash 将 string 转换为 int 的方式,引入了另一个问题:hash 冲突

map 的 key 是可以唯一表示的任意值,但 key 经过 hash 函数处理之后的数值却不是 1:1 的唯一关系,不同的 key 经过 hash 处理之后可能会得到相同的结果。

带着这些问题,我们来思考 go 语言中 map 的源码实现。对 go 有一点了解的,都应该知道 map 底层使用 bucket 做实际数据存储。这算的上一个 sharding 策略,通过对 key 做 hash 运算、和 bucket 做取余,来确定 key 应该属于哪一个 bucket。

这个比较好理解,假设我们提前创建了 5 个 bucket,最终所有的值都应该被分配到这 5 个 bucket 上,取余是常规的操作策略。

通过下图 473 行的变量 b 可以得出,map 初始分配的 bucket 是一整块连续的内存。 h.buckets 作为内存的首地址,加上间隔的内存空间,得出 b 的首地址。这种通过加减法来计算内存地址的方式,在底层会比较常见。

在这里插入图片描述
用来记录 sharding 信息的结构体是 hmap,包含了 sharding 相关的信息,从上面的角度来看,主要包含 hash 的散列函数以及底层 bucket 的数量。对应的 bmap 实际存储 bucket 的元信息。从 hmap 和 bmap 的命名上可以推断出 header 和 body 的关系。如果工作上需要设置一种 sharding 的存储,可能是为了降低加锁的开销,也同样需要 2 个数据结构来组合使用。

每个 bucket 最多可容纳 8 组键值对,而 hmap 中确定的 bucket 数量是固定的,或者说在一段时间内,bucket 的数量是固定的。我们对 key 做 hash 之后,如果发现大于 8 个以上的不同 key 同时命中其中的一个 bucket 怎么处理呢?

这里主要在于 hash 冲突的解决方案,go 中采用链表法来解决冲突。按照我们传统的 map 策略,会在表示键值对元素的结构体上存储链表的指针。大概就是下面图示的意思,键值 2008 和 2001 经过 hash 运算之后值都为 1,所以接了一个链表节点。

在这里插入图片描述

通过拉链的方式,其实还支持了数据可以无限插入的能力。hmap 中关联到的 bucket 数量是有限的,假设 hmap 中包含有 7 个 bucket,那最多也就容纳 56(7*8)个元素。如果再继续加入新的键值对,就需要通过拉链的方式,扩展 bmap 中的链表。

源码 go1.16.6/src/runtime/map.go:426 中 mapaccess1 给了我们一些启示,我们是如何联合 hmap 和 bmap 查询到键值的。既然是 2 层结构,那肯定是首先通过 hmap 计算出 key 所在的 bucket,然后再通过 bmap 查询具体的 key。

上面的描述,针对 bucket 和 bmap 在连贯性上可能会产生些误会,但其实这两个名字值得其实是同一个类型。所以,bmap 中的首字母 b 到底应该是 body 的含义,还是 bucket 的含义,有待商榷。

第一步,计算 key 所在的 bucket ,代码中的局部变量 b 是通过指针运算计算出来的,表达式 hash&m 确定 key 所在第几个块位置(内存块的尺寸等于 t.bucketsize)。

	hash := t.hasher(key, uintptr(h.hash0))
	m := bucketMask(h.B)
	b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
	if c := h.oldbuckets; c != nil {
		if !h.sameSizeGrow() {
			// There used to be half as many buckets; mask down one more power of two.
			m >>= 1
		}
		oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
		if !evacuated(oldb) {
			b = oldb
		}
	}

在表达式 if 中有变量 b 的覆写逻辑,条件是 h.oldbuckets 存在

第二步,在 bucket 中查找元素

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

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

相关文章

虹科分享 | 网络流量监控 | 构建大型捕获文件(Ⅱ)——Pcap分析仪:Allegro网络万用表的Pcap过滤器

上一期我们讨论的是如何使用Wireshark工具进行结构化搜索的技术,这一期我们将为大家进行介绍,我们该如何使用 Allegro 网络万用表来加快 pcap 分析器的工作。 前期回顾:构建大型捕获文件(Ⅰ)——Wireshark过滤器和其他…

VIVO应用商店APP侵权投诉流程

目录一、官方指引二、侵权投诉提交流程一、官方指引 https://dev.vivo.com.cn/documentCenter/doc/34 二、侵权投诉提交流程 登录 vivo 开放平台:https://dev.vivo.com.cn/,点击右下角“工单系统”: 业务类型选 “投诉举报类”&#xff0…

Redis-设置过期时间及淘汰策略

文章目录1. TTL2. 设置过期时间3. 删除过期key4. 淘汰策略Redis-设置过期时间及淘汰策略项目组使用的 Redis 服务器发出了内存不足报警,查了一些资料,记录下。 1. TTL 查看 Redis key 是否过期是 TTL 命令,或者登陆 Redis 客户端&#xff0…

重点算法排序之堆排序(下篇)

文章目录 一、堆排序的概念 1、1 堆的基本概念 1、2 堆的特性 二、堆排序的思路及代码实现 2、1 建堆 2、2 向下调整算法详解 2、3 建完堆后进行堆排序 2、3、1 排升序建大堆 2、3、2 建大堆后进行堆排序 三、堆排序的例题 2、1 例题1:堆排序 2、2 例题2&#x…

HTTP.sys远程代码执行漏洞修复

1.漏洞描述 Http.sys是Microsoft Windows处理HTTP请求的内核驱动程序。HTTP.sys会错误解析某些特殊构造的HTTP请求,导致远程代码执行漏洞。成功利用此漏洞后,攻击者可在System帐户上下文中执行任意代码。由于此漏洞存在于内核驱动程序中,攻击…

VMware下的虚拟机网络设置(NAT、桥接、仅主机)

在入门使用VMware搭建Linux的环境时,对于网络的设置时不可避免的,因为linux搭建完成后,或多或少的回去访问外部资源或者被外部资源访问。这时候设置的虚拟机网络连接方式就显得尤为重要,所以在这里整理了一下虚拟机的三种连接方式…

说说压缩文件“打开密码”的两种模式

我们知道,如果对压缩文件有保密需求,可以给压缩文件设置“打开密码”,通过密码才能查看压缩文件里的内容。那通过WinRAR设置的“打开密码”有两种模式,你知道吗?下面来具体说说。 模式一:可以看到压缩包的…

springboot集成mybatis

springboot集成mybatis 文章目录springboot集成mybatis前言一、初始化项目1.创建项目2.引入依赖3.创建实体类4.修改配置文件二、使用Mybatis1.纯注解方式2.使用xml文件方式三、使用pagehelper分页前言 MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibe…

赤池信息量准则(AIC)和贝叶斯信息准则(BIC)

一 AIC 赤池信息量准则(Akaike information criterion,AIC)是评估统计模型的复杂度和衡量统计模型“拟合”资料之优良性(Goodness of fit)的一种标准,是由日本统计学家赤池弘次创立和发展的。赤池信息量准则建立在信息熵的概念基…

LeetCode题目笔记——面试题 02.07. 链表相交

文章目录题目描述题目难度——简单方法一:数数,然后遍历代码/C方法二:双指针代码/C代码/Python总结题目描述 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c…

假期无聊,不如一起刷《剑指offer》(第六天)

剑指 Offer 41. 数据流中的中位数 剑指 Offer 41. 数据流中的中位数 这道题是求数据流的中位数,一般情况我们可以采用排序的方式很轻松的找出中位数。如果我们采用插入排序的话,每次插入数字的时间复杂度大概是O(N),怎么能让这个时间更短呢&a…

shell原理及Linux权限

shell及Linux权限 目录shell及Linux权限一、指令1.tar指令(重要)2.热键3.bc命令4.uname –r指令:5.关机6.以下命令作为扩展:二.shell命令以及运行原理三.权限1.权限的概念:2.Linux下有两种用户:超级用户(ro…

一图读懂mybatis 查询接口的源码流程

图比较大:如果看着比较糊的话,可以下载高清图:https://download.csdn.net/download/langwuzhe/87376216 第一步:创建 StatementHandler、ParameterHandler、ResultSetHandler-----------(三剑客的新生) 创建 StatementHandler 对…

WPS怎么转换PDF?保证你一学就会

相信大家在处理文件的时候肯定会使用到WPS文件,WPS文件包括Word、Excel、PPT文件,是我们经常使用的几种文件,有这几种文件我们可以更好的完成工作,但是在有些情况下,我们需要将WPS转换成PDF文件,这样就会更…

AS弹性伸缩简单介绍

AS 介绍 弹性伸缩(AutoScaling)是一种服务,可以自动调整弹性计算资源(ECS),以满足业务需求的变化。 弹性伸缩仅支持ECS实例或ECI实例数量的增加和减少,但不支持单个ECS实例或ECI实例的配置变更。 应用场景:弹性扩张、…

Windows安装使用Docker,方便你的开发和部署(DockerDesktop篇)

前言 首先声明,此篇不是完全的Docker技术文章,而是单纯的教你使用Docker,不包含Docker的一些命令、如何打包Docker镜像等等。 为什么要用Docker? 大家好,我是小简,今天带来一篇Windosw环境下使用Docker的…

女生学软件测试有什么优势么

在IT技术行业,女生学习软件测试还是有很大优势的。女生相较于男生更有耐心,包容性强,心思细腻,对细节把控更好,同时还能帮助团队男女平衡,活跃气氛。 软件测试是一个只要你肯学习就会有回报的职业&#xf…

判断用户输入的数字是奇数还是偶数

判断用户输入的数字是奇数还是偶数代码关键知识点 条件运算符, 相等运算符,为了让两个不同的数据类型(如number和string)的值可以作比较,必须要把一种类型转换为另一种类型(转换成相同的类型)&…

Ae 效果详解:CC Ball Action

Ae菜单:效果/模拟/CC Ball ActionEffect/Simulation/CC Ball ActionCC Ball Action (滚珠操作效果)可以将所有的像素变成小球模样,并且能够打破图层成球形网格。可通过摄像机观察其所具有的 3D 效果。◆ ◆ ◆效果控件属性说明S…

【数据结构与算法——C语言版】6. 排序算法(4)——快速排序

前言 本文介绍排序算法中的快速排序,快速排序是比较常用的一种排序算法,也是面试中经常会问到的一种排序算法,简称快排,是我们要介绍的第一种时间复杂度为O(nlogn)的排序算法。 核心思想 快速排序(Quick Sort)使用分治法策略&a…