go-map系统学习

news/2025/1/15 17:15:50/

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     uint8B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for detailshash0     uint32 // hash seedbuckets    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 growingnevacuate  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["张三"] = 90deleMap["小明"] = 100deleMap["王五"] = 60fmt.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.ppmy.cn/news/1526302.html

相关文章

泛零售行业的营销自动化现状如何?

​麦肯锡公司的数据显示,零售或快速消费品(CPG)行业中超过一半以上(54%)的工作有可能将被自动化所替代。 零售企业必须积极响应市场变化,通过技术创新、业务模式转型,提高运营效率、…

新能源汽车充电基础设施大爆发

新能源汽车充电基础设施迈入新阶段:全国总量破千万,未来五年将翻番增长 截至2024年7月底,全国充电设施总量已达到1060万台,为超过2500万辆新能源汽车提供了充电服务。目前,95%的高速公路服务区已具备充电功能&#xf…

小琳AI课堂:LLaMA 3.1 开源大模型的全新里程碑

引言 大家好,这里是小琳AI课堂!今天我们要聊的是Meta最新发布的开源大模型LLaMA 3.1。这个版本在AI界掀起了不小的波澜,不仅在参数规模上有显著提升,还在多项性能上实现了突破。让我们一起来看看LLaMA 3.1带来的新变化和意义吧&a…

08 - RTC实验

machine.RTC 类,作为 machine 模块下的一个重要硬件类,其主要设计目标在于对实时时钟(RTC)功能进行控制。实时时钟,无论是作为计算机体系内部或外部的硬件组件,其核心任务是提供准确无误的当前时间与日期信…

WPF的**逻辑树**和**可视树**。

WPF中有类似于前端技术中DOM(文档对象模型)的概念。在WPF中,这个概念被称为**逻辑树**和**可视树**。 1. **逻辑树**: - 逻辑树表示应用程序的结构,包括所有的控件和元素。它类似于前端中的DOM树,表示页…

数据结构基础详解:哈希表【理论计算篇】开放地址法_线性探测法_拉链法详解

文章目录 哈希表(散列表)1. 哈希表(散列表)的基本概念2. 常见的散列函数2.1 除留余数法2.2 直接定址法2.3 数字分析法2.4 平方取中法 3. 处理冲突的方法3.1 拉链法3.2 开放定址法3.2.1开放地址法的定义3.2.2 开放地址法的三种方法 3.3 再散列法(再哈希法…

【二十一】【QT开发应用】ListWiddget图标模式

代码 demo13_listwidget::demo13_listwidget(QWidget* parent): QWidget(parent) {ui.setupUi(this);resize(600, 500);QVBoxLayout* pMainVLayout new QVBoxLayout(this);QListWidget* pListWidget new QListWidget(this);pListWidget->setViewMode(QListView::IconMode…

【项目开发 | Python】基于“羊了个羊“风格的消除类小游戏

原创文章,不得转载。 目标:使用 Python 开发"羊了个羊"风格的消除类小游戏,合理运用 AIGC 工具提高开发效率;使用文生图工具实现图片设计等工作。 文章目录 项目背景项目介绍+项目展示游戏逻辑概述主界面游戏界面获胜界面失败界面附加功能项目细节项目测试测试样…