深度理解go中的Map

news2024/11/17 15:45:14

这里写自定义目录标题

  • 前言
  • map的内存模型
  • 增量扩容
  • 查找过程分析
  • 插入过程分析

前言

本篇将从底层讲解map的赋值、删除、查询、扩容的具体执行过程。结合源码,让你彻底明白map的原理。

map的内存模型

在源码中,表示map的结构体是hmap,其定义如下:

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 // optional fields
}

这里需要注意的是B是bucket数组的长度的对数,也就是说bucket数组的长度就是2^B。bucket里面存储的是key和value。buckets又是什么呢?其实它里面是一个指针,最终它指向的是一个结构体。定义如下

type bmap struct {
	tophash [bucketCnt]uint8
}

编译期间会给它加料,动态地创建一个结构

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

这里的bmap就是俗称的“桶”,可以看到桶最多装8个key。这些key之所以会落入同一个桶,是因为它们经过哈希计算后,哈希结果是一致的。在桶内,又会根据key计算出来的哈希值的高8位来确定key到底落入桶的哪个位置。

当map的key和value都不是指针,而且size小于128字节的情况下,会把bmap标记为不含指针,这样的好处就是避免了GC时扫描整个hmap。但是bmap中有一个overflow字段,它是指针类型。因此,这时候会把它移动到extra字段去。

type mapextra struct {
	// overflow[0] contains overflow buckets for hmap.buckets.
	// overflow[1] contains overflow buckets for hmap.oldbuckets.
	overflow [2]*[]*bmap

	// nextOverflow 包含空闲的 overflow bucket,这是预分配的 bucket
	nextOverflow *bmap
}

那么,bmap的组成是怎么样的,我们来看下
在这里插入图片描述
这就是bucket的内存模型,HOB Hash指的就是top hash。可以看到key和value是独自放在一起的。这样的好处是可以省略掉padding字段,从而节省了内存空间。
比如有一个map,比如map[int64]int8,按照key/value/key/value的方式存储,就需要额外padding7个字节。而所有的key,value分别绑定在一起,只需要在最后添加padding。这样避免了字节对齐造成的内存空间浪费。
每个bucket最多能放8个key-value对,如果需要把9个key-value落入当前的bucket,那就需要再构建一个bucket。bucket之间通过overflow指针连接起来。

增量扩容

哈希表的核心思想是空间换时间,访问速度是直接跟填充因子有关。当哈希表快满的时候需要进行扩容。假设,扩容前哈希表的大小是2^B 扩容后哈希表大小变成了2^(B+1),每次扩容都为原来的两倍,哈希表大小始终为2的指数倍。假设扩容之前容量是A,扩容后的容量为B,一般情况下hash mod A 不等于 hash mod B。因此扩容之后要计算每一项在哈希表中的位置,当hash表扩容之后要重新计算每一项在哈希表中的新位置。当hash表扩容之后,需要将那些旧的pair重新哈希到新的table上(源代码中称之为evacuate), 这个工作并没有在扩容之后一次性完成,而是逐步的完成(在insert和remove时每次搬移1-2个pair),Go语言使用的是增量扩容。
增量扩容的目的是为了缩短容器的响应时间。假设我们把map用来存储实时性要求比较高的应用服务器,如果不采用增量扩容的话,当map里面存储的元素很多之后,扩容时系统就会卡往,导致较长一段时间内无法响应请求。不过增量扩容本质上还是将总的扩容时间分摊到了每一次哈希操作上面。
扩容会建立一个原来2倍的新表,将旧的bucket搬到新的表中之后,并不会将旧的bucket从oldbucket中删除,而是加上一个已删除的标记。
由于这个工作是逐渐完成的,因此会造成一部分数据在old table中,一部分在new table中,new table在源码中又叫evacuated, 所以对于hash table的insert, remove, lookup操作的处理逻辑产生影响。只有当所有的bucket都从旧表移到新表之后,才会将oldbucket释放掉。
那么,扩容的填充因子该是多少呢?如果grow太频繁会导致空间利用率低,如果很久才grow,又会生成很多overflow buckets。查找的效率就会降低。在go中使用了一个宏控制的(#define LOAD 6.5), 它的意思是如果table中元素的个数大于table中能容纳的元素的个数, 那么就触发一次grow动作。

查找过程分析

1.根据key计算出hash值
2.如果存在old table则先在old table中查找,如果old table已经evacuated,转到步骤3。 反之,返回其对应的value。
3.在new table中查找对应的value。
我们来看看源码

do { //对每个桶b
    //依次比较桶内的每一项存放的tophash与所求的hash值高位是否相等
    for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
        if(b->tophash[i] == top) { 
            k2 = IK(h, k);
            t->key->alg->equal(&eq, t->key->size, key, k2);
            if(eq) { //相等的情况下再去做key比较...
                *keyp = k2;
                return IV(h, v);
            }
        }
    }
    b = b->overflow; //b设置为它的下一下溢出链
} while(b != nil);

插入过程分析

1.根据key算出hash值,进而得出对应的bucket。
2.如果bucket在old table中,将其重新散列到new table中。
3.在bucket中,查找空闲的位置,如果已经存在需要插入的key,更新其对应的value。
4.根据table中元素的个数,判断是否grow table。
5.如果对应的bucket已满,则重新申请新的bucket作为overbucket。
6.将key/value pair插入到bucket中。
此外,有两点需要注意,old bucket是被冻结的,意味着查找时会在old bucket中查找,但是不会对old bucket插入数据。如果在old bucket中查找到了相应的key,则将它迁移到new table,并加上evacuate标志。额外的还会迁移另外一个pair。
此外,只要在某个bucket中查找到第一个空位,就会将key/value插入到这个位置。找到了相同的key或者第一个空位就可以结束遍历。所以这样容易造成删除的时候得完全遍历bucket的所有溢出链,将所有的相同key数据都删除。所以目前map的设计是为插入而优化的,删除效率会比插入低一些。

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

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

相关文章

红队隧道应用篇之Burpsuite设置上游代理访问内网(五)

为何要设置上层代理 Burp Suite设置上游代理的主要原因是为了拦截和修改来自浏览器的请求。当您在使用Burp Suite进行Web应用程序安全测试时&#xff0c;您可能希望模拟攻击者发送恶意请求&#xff0c;以测试应用程序是否能够防御这些攻击。使用上游代理可以帮助您在浏览器和目…

Android OpenGL ES 学习(十) – GLSurfaceView 源码解析GL线程以及自定义 EGL

OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学习(三) – 绘制平面图形 Android OpenGL ES 学习(四) – 正交投影 Android OpenGL ES 学习(五) – 渐变色 Android OpenGL ES 学习(六) – 使用…

python自定义包实例

了解python中的异常捕获与传递请点击“python中的异常捕获与传递” 了解python中的模块与包详解请点击“python中的模块与包详解” 目录 一.实例&#xff1a;自定义包 二.详解 1.新建my_utils包 2.新建str_util.py和file_util.py两个python file 3.str_util.py中的代码 演…

移动设备软件开发-AlertDialog6种使用方法

AlertDialog 1.AlertDialog的6种创建模式 1.1setMessage 1&#xff09;Java代码 //1.创建构造器AlertDialog.Builder buildernew AlertDialog.Builder(this);//2.设置参数builder.setTitle("弹窗提示").setIcon(R.mipmap.boy).setMessage("选择你的性别&#xf…

什么是WMS系统?WMS系统有什么功能

科技进步促使的数字化转型正在为大多数行业铺平道路&#xff0c;并重新定义它们在各个方面的功能&#xff0c;物流行业也不例外&#xff0c;因为它见证了日常运营的重大转变。改变物流行业的关键之一就是WMS系统的引入。仓储一直是运输和物流部门的核心支柱&#xff0c;随着新工…

基于Vue+Express+Mysql开发的手机端电影购票系统(附源码)

基于VueExpressMysql开发的手机端电影购票系统 基于手机的电影购票系统-VueNode 一个VueExpressMysql的电影售票项目 项目完整源码下载 https://download.csdn.net/download/DeepLearning_/87327200 前端展示 后台展示 项目说明 项目目录 ├── film 前端页面项目文件 …

HTML CSS JS游戏网页设计作业「响应式高端游戏资讯bootstrap网站」

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

windows上datax的使用记录

datax使记录 简介 https://github.com/alibaba/DataX DataX 是阿里云 DataWorks数据集成 的开源版本&#xff0c;在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、Hive、ADS、HBase、TableStore(O…

[洛谷]P1449 后缀表达式

[洛谷]P1449 后缀表达式一、问题描述&#xff1a;题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示二、思路分析1、算法标签2、思路三、代码实现一、问题描述&#xff1a; 传送门&#xff1a;[洛谷]P1449 后缀表达式 题目描述 所谓后缀表达式是指这样的一个表达式…

DJ14 简单接口电路及应用

目录 一、I/O 接口 1. 接口和端口的关系 2. 接口的基本结构 3. 8086/8088 端口编址 二、简单接口芯片 1. 74LS244 三态门 2. 74LS273 锁存器 3. 74LS374锁存器 4. 综合应用 三、基本输入输出方式 1. 无条件传送方式 2. 查询工作方式 一、I/O 接口 1. 接口和端口的…

校招面试真题 | 你的期望薪资是多少?为什么?

很多人去面试的时候&#xff0c;就像打游戏&#xff0c;过五关斩六将&#xff0c;终于到最后一关了&#xff0c;但是谈薪资的难度堪比打游戏中搞定终级 boss 的难度&#xff0c;真的是太「南」了&#xff0c;好多人都是因为这个问题让自己五味杂陈呀。报高了怕好 offer 失之交臂…

基因编辑相关最新研究进展(2022年12月)

【1】西湖大学马丽佳团队开发新型CRISPR脱靶和DNA易位检测工具 2022-12-15报道&#xff0c;2022年12月12日&#xff0c;西湖大学生命科学学院马丽佳团队在 Nature Communications 期刊发表了题为&#xff1a;PEAC-seq adopts Prime Editor to detect CRISPR off-target and DN…

C++11标准模板(STL)- 算法(std::next_permutation)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 产生某个元素范围的按字典…

Spring Cloud 2022.0.0 正式发布,代号 “Kilburn“

Spring Cloud 2022.0.0 已正式发布。 获取地址&#xff1a;https://repo1.maven.org/maven2/org/springframework/cloud/spring-cloud-dependencies/2022.0.0/ Spring Cloud 为开发人员提供了工具&#xff0c;以快速构建分布式系统中的某些常见模式&#xff08;例如&#xff1a…

java中的垃圾回收算法

java中有四种垃圾回收算法&#xff0c;分别是&#xff1a; 标记清除法、标记整理法、复制算法、分代收集算法 1、标记清除法: 第一步:利用可达性去遍历内存&#xff0c;把存活对象和垃圾对象进行标记; 第二步:在遍历一遍&#xff0c;将所有标记的对象回收掉; 特点:效率不行…

Java+MySQL基于ssm的超市进销存会员管理系统

随着我国经济的高速增长,各类超市和便利店也是越来越多,超市和便利店的出现,方便了人们对于日常生活消费的需要,为了能够更好的对超市的顾客进行服务,大多数超市提出了会员的机制,通过这种机制来增加用户的黏度,在给用户提供更好的服务的同时也提高了营业额。 超市会员管理系统…

转行,你考虑清楚了吗?

“我为什么离开中石油”写完后&#xff0c;引发了不少人的共鸣&#xff0c;一些在工作中苦苦挣扎、渴望转行的朋友&#xff0c;在微信上询问我转行情况和转行建议。 非常感谢朋友们的关心和信任&#xff0c;然而我并非什么职业规划大师&#xff0c;只是一个在石油圈混了五年的…

ChatGPT进化的秘密

本文作者&#xff0c;符尧 yao.fued.ac.uk&#xff0c;爱丁堡大学 (University of Edinburgh) 博士生&#xff0c;本科毕业于北京大学&#xff0c;与彭昊&#xff0c;Tushar Khot 在艾伦人工智能研究院 (Allen Institute for AI) 共同完成英文原稿&#xff0c;与剑桥大学郭志江…

搞懂Redis 数据存储原理,别只会 set、get 了

我的核心模块如图 1-10。 图 1-10 Client 客户端&#xff0c;官方提供了 C 语言开发的客户端&#xff0c;可以发送命令&#xff0c;性能分析和测试等。 网络层事件驱动模型&#xff0c;基于 I/O 多路复用&#xff0c;封装了一个短小精悍的高性能 ae 库&#xff0c;全称是 a si…

【C语言】函数的声明_函数定义_函数调用_函数递归 [函数的基本使用]

文章目录前言1.函数是什么?2.C语言中函数的分类2.1 库函数2.2 自定义函数3.函数的参数3.1 实际参数&#xff08;实参&#xff09;&#xff1a;3.2 形式参数&#xff08;形参&#xff09;&#xff1a;4.函数的调用4.1 传值调用4.2 传址调用4.3 练习5.函数的嵌套调用和链式访问5…