go-map系统学习

news2025/1/12 21:59:59

map底层结构

Goland的map的底层结构使用hash实现,一个hash表里有多个hash表节点,即bucket,每个bucket保存了map中的一个或者一组键值对。

map结构定义:

runtime/map.go:hmap
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     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    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 growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

其中 buckets为 bucket数组指针,数组的大小为2^B

若B=2则,buckets数量是2^2=4

imghash结果示意图

bucket是桶,通常用来表示一类数据的集合。hash表里的bucket称之为哈希桶,通常是求余后结果相同的落入一个桶中。

根据key值计算出哈希值,取hash值的低8位与2^hmap.B取模确定存放数据到哪个bucket

bucket结构

bucket结构定义,逻辑上是如下的结果:

type bmap struct {
    tophash [8]uint8 //存储哈希值的高8位
    data    byte[1]  //key value数据:key/key/key/.../value/value/value...
    overflow *bmap   //溢出bucket的地址
}
  • tophash 是长度为8的数组,哈希值高位相同的分到同一个bucket中的键,存入当前bucket时候,会将哈希值的高位存在在该数组中,以方便后续匹配。

  • data这块其实是8个key、8个value,但是我们不能直接看到;为了优化对齐,go采用了key放在一起,value放在一起的存储方式。

  • overflow表示hash冲突发生时,下一个溢出桶的地址

上述中data和overflow并不是在结构体中显示定义的,实际包代码中定义也看不到,但是有这个逻辑存在。

实际源码定义可参考:https://cs.opensource.google/go/go/+/refs/tags/go1.23.0:src/runtime/map.go

这样的定义实现了:若某个哈希对应的bucket空间已满,则需要创建一个新的bmap存储键值对,无数个bmap通过overflow指针进行关联。buckets链条是类似于这样的结构:

bucket链条结构

hmap和bmap结构

结合以上hmap和bmap的结构,我们可以总结一个map基本结构:

img

负载因子

负载因子用来衡量一个哈希表冲突情况,公式为:

负载因子=键的数量/bucket数量

对于上图的负载因子:6/3=2

哈希表需要将负载因子控制在合适的大小,超过阈值需要进行rehash,也即键值对重新组织:

  • 哈希因子过小,说明空间利用率低
  • 哈希因子过大,说明冲突严重,存取效率低

Go语言负载因子达到6.5时就会触发rehash

hash扩容

新元素添加时候,会检查是否需要扩容,扩容条件:

  1. 负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。

  2. overflow数量 > 2^15时,也即overflow数量超过32768时。

增量式扩容

当负载因子过大,新建bucket,新的bucket长度是原2倍,旧bucket数据搬迁至新的bucket。搬迁过程采样逐步搬迁策略,即每次访问map都会触发一次搬迁,每次搬迁2个键值对。

渐进式扩容中,oldbuckets指向原来的桶。而buckets指向了新申请的bucket。新的键值对被插入新的bucket中。后续对map的访问操作会触发迁移,将oldbuckets中的键值对逐步的搬迁过来。当oldbuckets中的键值对全部搬迁完毕后,删除oldbuckets。

img

如上,bucket0已经有7个值,此时负载因子是7,超过6.5,当再存入一个key时,就会触发扩容。新建了bucket1,但是新建的bucket是buckets指向的,且在新的空间给bucket0留出位置,待一点点的将oldbuckets指向的空间数据迁移至新的bucket0空间,然后删除就得oldbuckets指向的空间。搬迁完成的空间示意图:

img

等量式扩容

所谓等量扩容,实际上并不是扩大容量。buckets数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以使bucket的使用率更高,进而保证更快的存取

map查找数据

  1. 首先根据key值计算出哈希值

  2. 取哈希值的低八位与2^hmap.B取模确定bucket位置

  3. 取高八位在桶数组中进行查找

  4. 如果tophash[i]中存储值也哈希值相等,则去找到该bucket中的key值进行比较

  5. 当前bucket没有找到,则继续从下个overflow的bucket中查找。

  6. 如果当前处于扩容搬迁过程,则优先从oldbuckets查找

map插入过程

  1. 根据key值算出哈希值
  2. 取哈希值低位与hmap.B取模确定bucket位置
  3. 查找该key是否已经存在,如果存在则直接更新值
  4. 如果没找到将key,将key插入

map操作

操作包括初始化、键的判断、遍历、作为函数参数使用

map声明与初始化

map是一种引用类型,有几种声明方法:

  1. 声明空的map
   var myMap map[string]int

这样声明的map是nil值,对它操作之前,必须使用make来初始化。

注意,struct结构中成员是map变量的话,在实际使用中必须再make初始化空间。

  1. 使用make直接声明+初始化
stringMap := make(map[string]string, n)

​ n表示长度,但是使用中是可以扩容的,相当于建议的长度。

  1. 使用字面量初始化
    myMap := map[string]int{  
       "one":   1,  
       "two":   2,  
       "three": 3,  
   }

:= 是一个特殊的赋值操作符,被称为“短变量声明”或“短变量赋值”。它主要用于在函数内部声明并初始化新的局部变量。使用 := 可以同时完成变量的声明和初始化,使得代码更加简洁。在全局作用域(即函数外部)不能使用 := 来声明变量,因为 := 会隐式地声明变量,而全局变量需要显式声明。

4,使用var关键字结合字面量来初始化

   var myMap = map[string]int{  
       "one":   1,  
       "two":   2,  
       "three": 3,  
   }

5,显示声明类型+初始化

   var myMap map[string]int = map[string]int{      "one":   1,      "two":   2,      "three": 3,   }

对于全局变量或需要在多个地方引用的变量,显式声明类型可能更加合适。

判断键是否存在

value, ok := sliceMap[key]

判断ok来判断值是否存在

  key := "小明"
   value, ok := deleMap[key]
   if ok {
   	fmt.Println("key:", key, "value:", value)
   } else {
   	fmt.Println("key:", key, "不存在")
   }

删除键值对

delete(mapName,key)

删除mapName map中的key值键值对

deleMap := make(map[string]int)
  deleMap["张三"] = 90

  deleMap["小明"] = 100

  deleMap["王五"] = 60



  fmt.Println("map:", deleMap)

  delete(deleMap, "小明")

  fmt.Println("map after delete:", deleMap)

delete是安全的操作,即使删除不存在的key也不报错。delete中如果查找删除失败将返回value类型对应的零值

	m1 := map[int]string{1: "sss", 2: "fff", 3: "zzz"}
	delete(m1, 5)

	for k, v := range m1 {
		fmt.Printf("%d ----> %s\n", k, v)
	}
//1 ----> sss
//2 ----> fff
//3 ----> zzz

遍历map

使用for range遍历map,第一个参数是key,第二是value

	for key, value := range scoreMap {
		fmt.Println(key, value)
	}

第二种遍历方法,只返回key,省略value

	for k := range scoreMap {
		fmt.Printf("%s----> %v\n", k, scoreMap[k])
	}

map长度

可以使用len函数获取map中键值对的个数:

 fmt.Println("len is:", len(stringMap))

map作为函数参数与返回值

map作为函数参数是引用传递

map作为函数返回值也是引用传递

	m1 := map[int]string{1: "sss", 2: "fff", 3: "zzz"}
	m2 := getAndChangeMap(m1)
	fmt.Println("map m2:", m2)
	fmt.Println("map m1:", m1)
	m2[4] = "yiyi"
	fmt.Println("change m2")
	fmt.Println("map m2:", m2)
	fmt.Println("map m1:", m1)
func getAndChangeMap(m map[int]string) map[int]string {
	m[23] = "lili"
	return m
}

打印内容

map m2: map[1:sss 2:fff 3:zzz 23:lili]
map m1: map[1:sss 2:fff 3:zzz 23:lili]
change m2
map m2: map[1:sss 2:fff 3:zzz 4:yiyi 23:lili]
map m1: map[1:sss 2:fff 3:zzz 4:yiyi 23:lili]

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

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

相关文章

Linux系统进程的优先级

一、进程优先级的概念 进程优先级就是进程被CPU执行的先后顺序,优先级值越小,优先级别越高。 使用ps -al命令查看当前系统所有进程的优先级: PRI是进程的基准优先级,NI(nice值)是进程优先级修正数据&…

【C++】理解C++中的复制、复制构造函数

十、理解C中的复制、复制构造函数 拷贝就是要复制数据,也就是复制内存。 当我们把一个对象或一段数据从一个地方拷贝到另一个地方,那这个对象或数据其实是有两个副本,而且这个过程还是需要时间和开销的。所以如果你只是想读取数据&#xff0…

SQL使用IN进行分组统计时如何将不存在的字段显示为0

这两天被扔过来一个脏活儿:做一个试点运行系统的运营指标统计。 活儿之所以称为“脏”,是因为要统计8家单位共12个项目的指标。而每个项目有3个用户类指标,以及分17个功能模块,每个功能模块又分5个维度的指标。也就是单个项目是1…

携手Vatee万腾平台,共赴智能时代新征程

在科技日新月异的今天,我们正站在一个前所未有的历史交汇点上——智能时代的大门已轰然洞开,万物互联、数据驱动、智能决策正逐步成为社会发展的新常态。在这场深刻的变革中,Vatee万腾平台以其前瞻性的视野、创新的技术实力以及深厚的行业积淀…

QtCreator学习(二).在stm32mp1中使用

0.配置编译环境 复制【正点原子】STM32MP157开发板(A盘)-基础资料\05、开发工具\01、交叉编译器st-example-image-qtwayland-openstlinux-weston-stm32mp1-x86_64-toolchain-3.1-snapshot.sh到虚拟机chmod添加可执行文件,./st*运行&#xff…

信号与线性系统综合实验

文章目录 一、实验目的二、实验内容及其结果分析(一)基础部分(二)拓展部分(三)应用设计部分 三、心得体会 一、实验目的 1、掌握连续时间信号与系统的时域、频域综合分析方法;   2、掌握运用M…

【数据库】MySQL-基础篇-多表查询

专栏文章索引:数据库 有问题可私聊:QQ:3375119339 目录 一、多表关系 1.一对多 2.多对多 3.一对一 二、多表查询概述 1.数据准备 2.概述 3.分类 三、内连接 1.隐式内连接 2.显式内连接 3.案例 四、外连接 1.左外连接 2.右外连…

力扣最热一百题——轮转数组

目录 题目链接:189. 轮转数组 - 力扣(LeetCode) 题目描述 示例 提示: 知识补充ArrayDeque () ArrayDeque 的特点: 常用方法: 详细示例: 运行结果: …

无刷直流电动机的匝间绝缘测试优化

近年来,随着消费者对高效、快速干发需求的增加,高速电吹风逐渐成为市场的宠儿。高速电吹风的关键技术之一便是无刷直流电动机,其转速可以高达100,000转/分钟以上,电压为DC310V。相比传统电吹风,高速电吹风在效率和用户…

java基于PDF底层内容流的解析对文本内容进行编辑

本文实现了基于坐标位置对PDF内容的底层修改而非覆盖,因此不会出现在某些高级PDF编辑器中可以移除插入内容或者文件随着编辑次数增多而大幅增大(原因是原内容还在文件中)的问题,而且使用的pdfbox是一个开源的、免费的PDF处理库&am…

如何使用 Vidu Studio 根据照片和提示词生成视频

在这个数字化时代,视频内容已经成为我们日常生活中不可或缺的一部分。无论是记录美好瞬间,还是制作创意短片,视频都能生动地呈现我们的故事。今天,我将向大家介绍如何使用 Vidu Studio,根据已有照片和提示词&#xff0…

保姆级CVE-2018-17066漏洞复现 DLink命令注入漏洞(更新完结)

参考文章 CVE-2018-17066复现-CSDN博客 IOT-CVE-2018-17066(D-Link命令注入漏洞)_firmae路由仿真-CSDN博客 https://www.cnblogs.com/from-zero/p/13300396.html IOT-CVE-2018-17066(D-Link命令注入漏洞)_iot设备漏洞-CSDN博客 cve-2018-17066复现 | 1uckycs blog 漏洞环境搭建…

web渗透—RCE

一:代码执行 相关函数 1、eval()函数 assert()函数 (1)原理:将用户提交或者传递的字符串当作php代码执行 (2)passby:单引号绕过:闭合注释;开启GPC的话就无法绕过(GPC就是将单引号转换为"反斜杠单引号"&a…

【Redis】缓存和数据库一致性问题及解决方案

往期文章: 【Redis】Redis 底层的数据结构(结合源码) 【Redis】为什么选择 Redis 做缓存? 【Redis】缓存击穿、缓存穿透、缓存雪崩原理以及多种解决方案 一、前言 在前面的文章中,我们探讨了为什么要使用 Redis…

独居打工人,把超市当顶配食堂

文 | 螳螂观察 作者 | 如意 独自在大城市扎根的年轻人有着自己的小确幸,比如“周末可以睡到下午才起床,不会有任何人打扰”,“瘫在沙发上吃着零食享受一部自己想看很久的电影,也不会被唠叨。” 但生活并不总是尽如人意&#xf…

基于SpringBoot+Vue的学生宿舍水电信息管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

PDF在线编辑哪家强?2024年4款热门工具大比拼

如今是数字化的时代,PDF 文件对我们工作和学习来说特别重要。不过呢,遇到那些麻烦的 PDF 编辑和转换的事情时,你是不是常常觉得没招儿,甚至还得加班到半夜?别犯愁啦,今天我给你讲讲四款非常好用的 PDF 在线…

深入理解全连接层:从线性代数到 PyTorch 中的 nn.Linear 和 nn.Parameter

文章目录 数学概念(全连接层,线性层)nn.Linear()nn.Parameter()Q1. 为什么 self.weight 的权重矩阵 shape 使用 ( out_features , in_features ) (\text{out\_features}, \text{in\_features}) (out_features,in_features)而不是 ( in_featur…

【人工智能】OpenAI最新发布的GPT-o1模型,和GPT-4o到底哪个更强?最新分析结果就在这里!

在人工智能的快速发展中,OpenAI的每一次新模型发布都引发了广泛的关注与讨论。2023年9月13日,OpenAI正式推出了名为o1的新模型,这一模型不仅是其系列“推理”模型中的首个代表,更是朝着类人人工智能迈进的重要一步。本文将综合分析…

10款超好用的电脑文件加密软件推荐|2024文件加密软件排行榜

在数字时代,数据安全已成为个人和企业不可忽视的重要议题。加密软件作为守护数据安全的坚固防线,其重要性不言而喻。以下是2024年备受推荐的十款电脑文件加密软件。 1.安秉网盾 安秉网盾以其全面的数据保护和安全防护功能备受企业青睐。它支持多种加密…