Go之map详解

map的结构

map实现的两个关键数据结构

  • hmap 定义了map的结构
  • bmap 定义了hmap.buckets中每个bucket的结构
// A header for a Go map.
type hmap struct {
	count     int // 元素的个数
	flags     uint8 // 状态标记,标记map当前状态,是否正在写入
	B         uint8   // 可以最多容纳 6.5 * 2 ^ B 个元素,6.5为装载因子
	noverflow uint16 // 溢出的个数
	hash0     uint32 // 哈希种子

	buckets    unsafe.Pointer // 桶的地址
	oldbuckets unsafe.Pointer // 旧桶的地址,用于扩容
	nevacuate  uintptr        // 迁移进度,小于nevacuate的已经迁移完成

	extra *mapextra // optional fields
}
// A bucket for a Go map.
type bmap struct {
     //每个元素hash值的高8位,如果tophash[0] < minTopHash,表示这个桶的搬迁状态
	tophash [bucketCnt]uint8
    // 接下来是8个key、8个value,但是我们不能直接看到;为了优化对齐,go采用了key放在一起,value放在一起的存储方式,
    // 再接下来是hash冲突发生时,下一个溢出桶的地址
}
//上面bmap结构是静态结构,在编译过程中runtime.bmap会拓展成以下结构体:
type bmap struct{
	tophash [8]uint8
	keys [8]keytype
	// keytype由编译器编译时候确定
	values [8]elemtype
	// elemtype由编译器编译时候确定
	overflow uintptr
	//overflow的下一个bmap,overflow是uintptr而不是*bmap类型,保证bmap完全不含指针,是为了减少gc,溢出桶存储到extra字段中
}

名词解释
负载因子

负载因子是衡量hash表中当前空间占用率的指标。在go中,就是平均每个bucket存储的元素个数。

  • 计算公式如下:

LoadFactor(负载因子)= hash表中已存储的键值对的总数量/hash桶的个数(即hmap结构中buckets数组的个数)在各语言的实现中,都会确定一个负载因子的阈值,当负载因子超过这个阈值时,就要进行扩容,以平衡存储空间和查找元素时的性能。根据golang团队的测试数据,将负载因子定为了6.5,即平均每个bucket保存的元素≥6.5个时会触发扩容。

B

bucket个数为:2^B; 可以保存的元素数量是 负载因子 * 2^B。

data := make(map[int]int,17)

根据计算公式

初始元素个数 ≤ 2^B * 6.5
172^2 * 6.5

可以计算出B为2,初始的桶的个数为4
其中:
B<4时,根据B创建桶的个数的规则为:2^B(标准桶)
B>=4时,根据B创建桶的个数的规则为:2^B + 2^(B-4) (标准桶+溢出桶)

tophash

tophash是一个长度为8的数组,它不仅仅用来存放key的哈希高8位,在不同场景下它还可以标记迁移状态,bucket是否为空等。弄懂tophash对我们深入了解map实现非常重要。

当tophash对应的K/V被使用时,存的是key的哈希值的高8位;当tophash对应的K/V未被使用时,存的是K/V对应位置的状态。

emptyRest      = 0 
emptyOne       = 1 
evacuatedX     = 2 
evacuatedY     = 3
evacuatedEmpty = 4
minTopHash     = 5 

当tophash[i] < 5时,表示存的是状态;
当tophash[i] >= 5时,表示存的是哈希值;

当计算的哈希值小于minTopHash时,会直接在原有哈希值基础上加上minTopHash,确保哈希值一定大于minTopHash。

func tophash(hash uintptr) uint8 {
  	top := uint8(hash >> (sys.PtrSize*8 - 8))
	if top < minTopHash {
    	top += minTopHash
  	}
	return top
}
emptyRest

这个值有两层意思:一是表示该tophash对应的K/V位置是可用的;二是表示该位置后面的K/V位置都是可用的。

赋值:

初始化的时,tophash会被置为emptyRest。

删除map元素时,会判断是否需要把删除key对应的tophash置为emptyRest。

作用:

判断bucket是否为空

当tophash[0]==emptyRest表示整个bucket都是空的,这就是源码里面判断bucket是否为空的方法。

查找时快速判断后面位置是否还需遍历

如在查找时,在一个bucket中,找到tophash[2]位置,发现值为emptyRest,就可以判断该bucket没有该元素,继续查找下一个bucket。

emptyOne

仅表示该tophash对应的K/V位置是可用的,其后面的是否可用不知道。

赋值:

删除map元素时,会把key对应的tophash先置为emptyOne,再继续判断是否需要置为emptyRest。

evacuatedX && evacuatedY

这两个状态与扩容有关,记录元素被迁移到了新桶的部位–X或Y。如果是等位迁移,旧桶的元素必然被迁移到X部;如果是扩容迁移,旧桶元素可能迁移到X部,也可能迁移到Y部。当迁移到X部时,旧桶tophash置为evacuatedX;当迁移到Y部时,旧桶tophash置为evacuatedY。

举个例子说明:扩容迁移,要把旧桶1的元素迁到新桶,因为新桶长度增长了一倍,因此旧桶1元素可能被迁移到新桶的1或5。当元素迁移到了1时,把旧桶tophash置为evacuatedX;反之,迁移到了5时,tophash置为evacuatedY。要注意置的是旧桶的tophash。

旧桶迁移完就被回收了,为啥还要记录每个元素迁移的位置?

想了解原因,我们必须要考虑一个很复杂的场景:遍历map时,开始扩容。map遍历并不是原子操作,在遍历过程中会有数据插入、删除等,会导致map扩容。因为遍历发生在扩容前,因此一直是遍历老桶。这时有两种情况:老桶数据没有迁移,这时直接从老桶返回K/V就可以了;老桶数据已经迁移,这时就需要重新查找map。那怎么判断老桶数据是否迁移?这时就用到evacuatedX和evacuatedY,这两个就是用来标记老桶数据迁移状态的。

evacuatedEmpty

用于表示此单元已迁移

创建map

在这里插入图片描述

func makemap_small() *hmap
func makemap(t *maptype, hint int, h *hmap) *hmap
func makemap64(t *maptype, hint int64, h *hmap) *hmap // hint类型为int64, 实质还是调用的 makemap

当创建map时不指定hint大小,那么调用makemap_small来进行创建 当指定了hint(代表初始化时可以保存的元素的个数)的大小的时候,若hint<=8, 使用makemap_small进行创建map,否则使用makemap创建map

map查找

Go map会在编译阶段转换成runtime包中的hmap。其中,bmap指向存储key-value的结构(数组)。数组元素为“桶”,每个桶中包含高8位的hash和相应的8个key-value,高8位hash用来快速找到目标key,其次是8个key,8个value(key和value分开存储,是为了防止key存储空间大于value时,value会自动占用key大小的空间,这样做可以减少空间的浪费),最后是指向溢出桶的指针(解决哈希冲突)。hash 表通过 hash 值的低几位(原理是对数组长度取余,但通常采用与运算来加速)去查找 hash 桶,然后在去查找到的 hash 桶中查找高8位的hash,快速锁定key,知道key,就可以获取其value了。如果遇到哈希冲突,即不同key产生的hash值一样,如此就需要额外进行key值的比较,这就要求存储的key值是可以比较相等的,
在这里插入图片描述
Go map扩容,数据迁移不是一次性迁移,而是等到访问到具体某个bucket时才将数据从旧bucket中迁移到新bucket中

  1. 一次性迁移会涉及到cpu资源和内存资源的占用,在数据量较大时,会有较大的延时,影响正常业务逻辑。因此Go采用渐进式的数据迁移,每次最多迁移两个bucket的数据到新的buckets中(一个是当前访问key所在的bucket,然后再多迁移一个bucket)
  2. cpu资源:扩容时需要迁移map中oldbuckets的元素,其中的rehash操作会消耗cpu的计算资源,有可能会影响到用户协程的调度
map插入/删除

在这里插入图片描述
在这里插入图片描述

扩容的条件:

1. 超过负载 map元素个数>6.5*桶个数
func overLoadFactor(count int, B uint8) bool{
	return count > bucketCnt && uintptr(count)>loadFactor*bucketShift(B)
}
其中
bucketCnt=8,一个桶可以装的最大元素个数
loadFactor=6.5,负载因子,平均每个桶的元素个数
bucketShift(8), 桶的个数
2. 溢出桶太多
当桶总数<2^15时,如果溢出桶总数>=桶总数,则认为溢出桶过多
当桶总数>=2^15时,直接与2^15比较,当溢出桶总数>=2^15时,即认为溢出桶太多了

扩容机制:

1.双倍扩容:针对条件1,新建一个buckets数组,新的buckets大小是原来的2倍,然后旧的buckets数据
搬迁到新的buckets
2.等量扩容:针对条件2,并不扩大容量,buckets数量维持不变,重新做一遍类似双倍扩容的搬迁操作,
把松散的键值对重新排列一次,使得同一个bucket中的key排列地更紧密,节省空间,提高buckets利用
率,进而保证更快的存取。该方法我们称之为等量扩容。

扩容过程:
在这里插入图片描述

假设旧桶数组大小为2^B, 新桶数组大小为2*2^B,对于某个hash值X
若 X & (2^B) == 0,说明 X < 2^B,那么它将落入与旧桶集合相同的索引xi中;
否则,它将落入xi + 2^B中

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

<计算机网络自顶向下> 可靠数据传输的原理(未完成)

可靠数据传输&#xff08;rdt&#xff1a;Reliable Data Transfer&#xff09;的原理 rdt在应用层&#xff0c;传输层和数据链路层都很重要是网络TOP10问题之一信道的不可靠特点决定了可靠数据传输rdt的复杂性rdt_send: 被上层&#xff08;如应用层&#xff09;调用&#xff0…

41.缺失的第一个正数

1. 解题原理&#xff1a; &#xff08;1&#xff09;对于一个有序的、不缺失元素的正数数组nums&#xff0c;元素nums[i]应当位于nums[i]-1的位置处。 &#xff08;2&#xff09;nums数组的长度为N&#xff0c;缺失的第一个正数如果不位于[1,N]&#xff0c;那么就肯定是N1 2. …

excel表格怎么设置密码?excel文件加密的两个方法

一、加密码的原理​ Excel加密码的原理主要基于加密算法和密钥管理。当用户为Excel文件或工作表设置密码时&#xff0c;Excel会采用一种加密算法对文件或工作表进行加密处理。这种加密算法通常是对称加密算法&#xff0c;如AES(高级加密标准)或DES(数据加密标准)。 二&#x…

海外住宅代理:推特账号为何容易被关小黑屋?

推特是全球最受欢迎的社交媒体之一&#xff0c;每天都有数以百万计的用户在这个平台上发布信息、分享观点和交流互动。然而&#xff0c;有些用户可能会发现他们的推特账号不幸陷入了所谓的“关小黑屋”状态&#xff0c;即账号被限制了可见度&#xff0c;导致发布的内容无法被其…

【数据分析面试】24.20个数据库问答题 (考察数据开发和实际应用能力)

作为数据从业者&#xff0c;日常工作除了对各类业务数据进行分析挖掘&#xff0c;也需要经常和数据库打交道、甚至也少不了要承担一些数据开发、数仓管理的工作。掌握数据库管理的基本概念和技术是至关重要的。无论是初学者还是从业者&#xff0c;理解数据库索引、范式、事务、…

四.音视频编辑-音频混合-概述

引言 当我们在前两篇博客中成功地构建了一个媒体组合&#xff0c;并且略过了音频部分时&#xff0c;我们意识到了我们需要对这个项目进行更详细的探讨。在本篇博客中&#xff0c;我们将会展示如何创建一个包含视频轨道、配音音频轨道以及背景音频轨道的完整媒体组合。更进一步…

游泳耳机哪个牌子好?体验与口碑兼顾的4大游泳耳机汇总!

最近的天气越来越炎热了&#xff0c;许多人选择游泳作为一种既能锻炼身体又能享受清凉的活动。而随着科技的发展&#xff0c;越来越多的运动爱好者希望在游泳时也能享受到音乐的乐趣。因此&#xff0c;游泳耳机应运而生&#xff0c;成为市场上的热门产品。然而&#xff0c;面对…

项目中的解耦小能手-观察者模式

目录 1.使用场景 2.什么是观察模式 3.观察者模式结构图 4.代码实现案例 4.1 subject代码实现 4.2 Observer类代码实现 5. 回顾总结 1.使用场景 当一个对象的改变需要同事改变其他对象的时候&#xff0c;如&#xff1a;订单中心-下单成功需要通知库存、物流和积分去做相应…

交流回馈老化测试负载优点和应用

交流回馈老化测试负载是用于模拟真实环境下设备运行状态的测试工具&#xff0c;通过对设备进行长时间的连续工作&#xff0c;以检测其性能的稳定性和可靠性。这种测试负载具有许多优点&#xff0c;并且在实际应用中有着广泛的用途。 在实际应用中&#xff0c;设备往往需要在各种…

Flask实战

from flask import Flask appFlask(__name__)点击Flask同时点击键盘ctrl即可查看Flask的默认初始化函数 def __init__(self,import_name: str,static_url_path: str | None None,static_folder: str | os.PathLike[str] | None "static",static_host: str | None …

产品心理学:为什么管钱的都是女生?

大家发现了吗&#xff1f;大部分公司女财务居多&#xff0c;而在家庭中&#xff0c;多数也是女生管钱。 为什么管钱的都是女生&#xff1f;答案文尾揭晓。 问题的答案&#xff0c;要从一个心理学名词“过度自信偏差”说起 用人话说&#xff0c;就是“迷之自信” 过度自信的例…

【剪映专业版】11音频的全流程剪辑操作

视频课程&#xff1a;B站有知公开课【剪映电脑版教程】 1.音乐素材 可能包含人声&#xff0c;音乐素材普遍比较长&#xff0c;几十秒到几分钟。要点击倒三角才会出现分类。 点击下载箭头下载素材&#xff1b;点击加号将素材增加到轨道&#xff1b;时间指示器在哪个地方&#…

Python | Leetcode Python题解之第35题搜索插入位置

题目&#xff1a; 题解&#xff1a; class Solution:def searchInsert(self, nums: List[int], target: int) -> int:left, right 0, len(nums) #采用左闭右开区间[left,right)while left < right: # 右开所以不能有,区间不存在mid left (right - left)//2 # 防止溢出…

UE5增强输入系统 Enhanced Input

关键字&#xff1a; Enhanced Input 、 输入、映射、事件、鼠标、键盘、键鼠、动作、Trigger、触发器、 疑问&#xff1a; 新输入系统怎么做一个基础的案例&#xff1f;Trigger修改器中每个项都是什么功能&#xff1f;InputAction和InputMappingContext中都有修改器&#xff…

Python基础02-掌握HTTP API的秘诀

在下面文案基础上扩展&#xff0c;写一篇技术博客&#xff0c;标题要有吸引力&#xff1f; 标题&#xff1a; 在Python中&#xff0c;使用HTTP API已成为一种常见的操作。本文将深入探讨如何使用Python的requests库与HTTP API进行交互。我们将学习如何发送GET和POST请求、处理…

消息队列选型(RabbitMq、RocketMq、Kafaka)

文章目录 前言RabbitMq优点缺点 RocketMq优点缺点 Kafaka优点缺点 总结 前言 当引入消息队列时&#xff0c;常见的选择包括ActiveMQ、Kafka、RabbitMQ和RocketMQ。然而&#xff0c;近年来&#xff0c;ActiveMQ的活跃度已经下降&#xff0c;很多公司已经不再使用这款消息队列中…

TSINGSEE青犀算法中台消防通道堵塞/占压AI检测算法的介绍及应用

消防通道是建筑物内用于紧急疏散的通道&#xff0c;其畅通无阻对于保障人员生命安全至关重要。然而&#xff0c;由于各种原因&#xff0c;消防通道经常会被杂物、车辆等堵塞&#xff0c;一旦发生火灾等紧急情况&#xff0c;后果不堪设想。为了有效解决这一问题&#xff0c;我们…

【氮化镓】GaN HEMT失效物理和可靠性

概述: 本文是一篇关于AlGaN/GaN基高电子迁移率晶体管(HEMTs)的失效物理和可靠性研究的综述文章,发表在2013年10月的《IEEE Transactions on Electron Devices》上。文章由Enrico Zanoni等人撰写,主要关注了影响栅极边缘和肖特基结的失效机制,并探讨了提高这些器件可靠性…

文档加密软件哪个好用?为什么迅软DSE加密软件更受用户青睐?

通过对文档内容进行加密处理&#xff0c;以确保其安全性和保密性。文档加密软件采用加密算法对文档进行加密处理&#xff0c;在加密过程中&#xff0c;文档加密软件会将文档的原始内容转换为一种不可读的形式&#xff0c;即加密后的文档。这个加密过程是通过应用特定的加密算法…

SQVI创建以及生成程序

SAP数据快速查询工具&#xff1a;Sqvi-QuickView 项目实施&运维阶段&#xff0c;为了快速获取一些透明表数据&#xff0c;一开始接触项目肯定会通过大量的数据表查找&#xff0c;然后线下通过EXCEL通过VLOOKUP进行数据关联&#xff0c;这种方式在关联数据较少的情况比较适应…
最新文章