go-map系统学习

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.mzph.cn/web/52663.shtml

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

相关文章

win10 安装多个版本的python

1,安装python3.9 和python3.10 2, 安装完之后分别打开两个版本的Python的安装目录(第一层目录),把pythonw.exe分别重命名为pythonw_39.exe和pythonw_310.exe,把python.exe复制一份,并分别重命名为python_…

【C++】_stack和_queue容器适配器、_deque

当别人都在关注你飞的有多高的时候,只有父母在关心你飞的累不累。💓💓💓 目录 ✨说在前面 🍋知识点一:stack •🌰1.stack介绍 •🌰2.stack的基本操作 🍋知识点二&…

怎么利用XML发送物流快递通知短信

现如今短信平台越来越普遍了,而短信通知也分很多种,例如服务通知、订单通知、交易短信通知、会议通知等。而短信平台在物流行业通知这一块作用也很大。在家时:我们平时快递到了,如果电话联系不到本人,就会放到代收点,然…

力扣之1777.每家商店的产品价格

文章目录 1. 1777.每家商店的产品价格1.1 题干1.2 建表1.3 题解1.4 结果截图 1. 1777.每家商店的产品价格 1.1 题干 表:Products -------------------- | Column Name | Type | -------------------- | product_id | int | | store | enum | | price | int | ---…

分布式调度方案:Elastic-Job

文章目录 一、什么是分布式调度二、Elastic-Job 介绍三、Elastic-Job 实战3.1 环境搭建3.1.1 本地部署3.1.2 服务器部署3.1.3 Zookeeper 管控台界面 3.2 入门案例3.3 SpringBoot 集成 Elastic-Job3.4 任务分片(★)3.5 Dataflow 类型调度任务 一、什么是分…

提升LLM能力表现的四种AI代理策略

欢迎来到AI产品经理从0到1研习之旅。 “AI Agent”系列的第二篇,它来了!我说过,AI Agent才是我最看好的(大模型落地应用)未来,嘿嘿,目测有很多大佬与我有相似的看法。 本文从吴恩达&#xff0…

Python | Leetcode Python题解之第401题二进制手表

题目&#xff1a; 题解&#xff1a; class Solution:def readBinaryWatch(self, turnedOn: int) -> List[str]:ans list()for i in range(1024):h, m i >> 6, i & 0x3f # 用位运算取出高 4 位和低 6 位if h < 12 and m < 60 and bin(i).count("1&…

浙大数据结构:03-树2 List Leaves

这道题我借用了一点上一题的代码思路&#xff0c;这题考察的主要是层序遍历&#xff0c;即用队列来实现&#xff0c;当然此处我依然采用数组模拟队列来实现。 机翻 1、条件准备 map的键存下标&#xff0c;后面值分别存左右子树的下标&#xff0c;没有子树就存-1. head数组只…

产业园服务体系建设,是否已全面覆盖企业成长的每一个阶段?

在当今竞争激烈的商业环境中&#xff0c;产业园作为企业发展的重要载体&#xff0c;其服务体系的完善程度至关重要。那么&#xff0c;产业园服务体系建设&#xff0c;是否已全面覆盖企业成长的每一个阶段呢&#xff1f; 从企业的初创期来看&#xff0c;产业园可以提供办公场地的…

数据结构与算法学习day21-二叉树

一、从中序与后序遍历序列构造二叉树 1.题目 106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 2.思路 以后序&#xff08;左右中&#xff09;数组的最后一个元素为切割点&#xff08;根节点&#xff09;&#xff0c;切割的节点切中序&#xf…

初学者指南:MyBatis 入门教程

主要介绍了Mybatis的基本使用、JDBC、数据库连接池、lombok注解&#xff01; 文章目录 前言 什么是Mybatis? 快速入门 使用Mybatis查询所有的用户信息 配置SQL提示 JDBC介绍 Mybatis 数据库连接池 lombok 总结 前言 主要介绍了Mybatis的基本使用、JDBC、数据库连接…

Tableau学习日记

Day1&#xff1a;Tableau简介、条形图与直方图 1.Tableau绘制条形图 1.1 条形图1&#xff1a;各地区酒店数量 1.2 条形图2&#xff1a;各地区酒店均价 1.3 堆积图&#xff1a;价格等级堆积图 2.Tableau绘制直方图 2.1创建评分直方图 Day2&#xff1a;Tableau简介、条形图与直…

Flutter类

Dart中的对象都继承自 Object 类&#xff0c;单继承&#xff08;extend关键字&#xff09;。Dart与Java、kotlin不同的是其无public、private、protected修饰符&#xff0c;默认public &#xff0c;通过在属性名、方法名前加 _下划线 来定义是否私有。 实现一个简单的类 class…

pod被驱逐追溯根因

背景 收到pod报警&#xff0c;看到其状态是error&#xff0c;新的pod又在另外一台机器上起来了。 原因分析 describe pod看到pod被驱逐 Status: Failed Reason: Evicted Message: The node was low on resource: ephemeral-storage. Containe…

数据结构之红黑树的 “奥秘“

目录&#xff1a; 一.红黑树概念 二. 红黑树的性质 三.红黑树的实现 四.红黑树验证 五.AVL树和红黑树的比较 一.红黑树概念 1.红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何 一条从根…

YOLO配合 PYQT做自定义虚拟电子围-自定义绘制多边形虚拟电子围栏

电子围栏标注以及显示 1、目标检测&#xff1a; YOLO可以识别检测物体&#xff0c;这是众所周知的。使用YOLO来做目标检测&#xff0c;并获取坐标信息。 2、电子围栏 比如在监控中&#xff0c;指定一块区域&#xff0c;如果有目标进入&#xff0c;则发出警报&#xff0c;并提…

《基于深度半监督学习的目标检测综述》泛读

基于深度半监督学习的目标检测方法分为 1、生成式方法 2、一致性正则化方法 3、基于图的方法 4、伪标记方法和混合方法 然后基于常用数据集 对典型方法进行了性能对比&#xff0c;最后分析了其挑战和发展趋势&#xff0c;旨在为相关研究提供参考 收获就是&#xff1a; 1…

JMeter 入门之远程启动,服务模式,多机联测,负载均衡测试

本文主要介绍 JMeter 远程启动及使用多节点完成大并发测试&#xff08;负载均衡测试&#xff09;&#xff0c;主打一个压力山大&#xff0c;借用 黑神话&#xff1a;悟空 的技能来描述就是远程开大&#xff0c;释放猴子猴孙技能。 搜了一些 jmeter 的案例或教程&#xff0c;讲的…

MyBatis-Plus分页查询、分组查询

目录 准备工作1. 实体类2. Mapper类3. 分页插件4. 数据 分页查询1. 使用条件构造器2. 使用自定义sql 分组查询1. 分组结果类2. 自定义sql3. 测试类 准备工作 1. 实体类 对地址字段address使用字段类型转换器&#xff0c;将List转为字符串数组保存在数据库中 package com.exa…

Docker 部署 Redis (图文并茂超详细)

部署 Redis ( Docker ) [Step 1] : 拉取 Redis 镜像, 推荐使用 7 的 Redis 版本 docker pull redis:7.0.12[Step 2] : 创建 Redis 相关目录 ➡️ 启动 Redis 容器 ➡️ 拷贝文件 ➡️ 授权文件夹 ➡️ 删除容器 # 创建 Redis 相关目录 mkdir -p /data/redis/{conf,data,log…