Golang Map原理(底层结构、查找/新增/删除、扩缩容)

2023-11-15

参考:

一、Go Map底层结构:

Go map的底层实现是一个哈希表数组 + 链表),使用拉链法消除哈希冲突,因此实现map的过程实际上就是实现哈希表的过程。

先来看下go map底层的具体结构:

type hmap struct {
	count      int            // 元素个数,调用len(map)返回这个值
	B          uint8          // bucket数量是2^B, 最多可以放 loadFactor * 2^B 个元素,再多就要扩容了
	hash0      uint32         // hash seed
	buckets    unsafe.Pointer // 指向bucket数组的指针(存储key val);大小:2^B 
	oldbuckets unsafe.Pointer // 扩容时,buckets 长度是 oldbuckets 的两倍
	// ...
}
type bmap struct {
	topbits  [8]uint8     // 高位哈希值数组
	keys     [8]keytype   // 存储key的数组
	values   [8]valuetype // 存储val的数组
	overflow uintptr      // 指向当前bucket的溢出桶
	// 为缓解当存在多个key计算后的哈希值低8位相同的个数大于一个bucket所能存放的数目8个时,且这个map还没达到扩容条件时,做的一种存储设计。
}

在这里插入图片描述
在这个哈希表中,主要涉及到的结构体有两个:一个是 hmap(a header for a go map),一个是 bmap(a bucket for a go map):

  • 对于 hmap,我们只需要关注其中的 buckets,它是一个指向 bmap结构体类型数组的指针。
    • 而对于其中的 bmap
      • 高位哈希值 topbits:数组记录的是当前bucket中key相关的 “索引”
      • 指向扩容bucket的指针 overflow:每个 bmap类型的 bucket 最多只能放 8个k-v键值对。如果碰巧有key的哈希值一样的新数据存入当前bucket,那就需要再构建一个新的溢出桶 bucket,并通过overflow指针连接起来,使得bucket形成一个链表结构。
      • 存储key/value的数组 keysvalues

二、key-value是如何存放的:

当前bucket桶中的 key-value 的值的存放是有其特点的,bucket桶中所有的key存放到 keys数组中,而所有的value存放到 values数组中。
这么做的原因也很简单,可以在key和value的长度不同时,消除padding(内存对齐)带来的空间浪费。具体如图所示:
在这里插入图片描述

三、根据key 查找/新增 数据:

对传来的key进行哈希运算得到唯一哈希值,并将该哈希值分为高位和低位,如图所示:
在这里插入图片描述
蓝色为高位,红色为低位。 低位用于寻找当前key属于哪个bucket,而高位用于寻找对应bucket中的具体key

而之前 bmap中的高位哈希值数组字段 topbits,存的就是当前bucket桶中不同key-value键值对中对应key的高位哈希值,这样便于根据key查找数据。

新增的过程与查找过程类似,也是填充桶的过程。

四、删除map中的数据

针对map中的key-value数据:

  • 如果是指针类型数据,则将其原有引用去除,利用go GC来清理内存
  • 如果是类型数据,则直接清理对应内存空间

最后将该key-value记录对应的 【bmap中高位哈希值数组 topbits】中的key相关 “索引” 置空。

五、map的扩容

当go map中每个bucket桶存储的平均元素个数大于加载因子 loadFactor = 6.5(判断扩容的条件)时,map底层就会创建一个容量大小是原来2倍的新buckets数组,并将 oldbuckets指针指向原来的旧buckets数组。然后,对旧buckets数组中的元素key重新哈希(rehash)得到新的哈希值,根据新的哈希值的高位和低位来放入扩容后的新buckets数组中。

加载因子越小↓,说明空间利用率低,因此 “产生冲突的机会” 低;
加载因子越大↑,说明空间利用率高,但是 “产生冲突的机会” 也高了。

不过需要注意的是:

并不是立刻把 oldbuckets指针所指向的旧bucket数组中的元素一次性转移到新的bucket数组当中,而是当只有访问到具体某个key所在的bucket时,才会将该bucket中的旧数据逐步迁移到新bucket中。一直到旧数据完全迁移完,才会删除 oldbuckets的指向,使得旧buckets空间得到释放。如下图所示:
在这里插入图片描述
这里迁移完并不会直接删除旧bucket中的数据,而是把原来旧数据的引用去掉,利用GC逐步清除内存
最终迁移完成后,会释放oldbuckets指针指向的旧哈希表占用的内存空间。

六、map的等量扩容(缩容)

map中数据较少,overflow 指向的溢出桶bucket数量过多时,会导致溢出桶中的记录存储很稀疏,排列不紧凑,大量空间被浪费。这时就需要进行等量扩容/缩容(一般出现在之前数据被大量删除的场景下)。

其实就是重新整理一下数据,使溢出桶中的数据重新紧凑的放在普通bucket桶中,避免不必要的空间浪费。

七、map的渐进式扩容

Go Map 扩容,做数据迁移时为何不一次性迁移数据,而是等到访问到具体某个bucket时才将数据从旧bucket中迁移到新bucket中?

  • 一次性迁移会涉及到cpu资源和内存资源的占用,在数据量较大时,会有较大的延时,影响正常业务逻辑。因此Go采用渐进式的数据迁移,每次最多迁移两个bucket的数据到新的buckets中(一个是当前访问key所在的bucket,然后再多迁移一个bucket)。
  • 尤其是cpu资源,扩容时需要迁移map中oldbuckets的元素,其中的 rehash 操作(计算键在新哈希表中新的位置)会消耗cpu的计算资源,有可能会影响到用户协程的调度
  • 内存资源,因为最终还是会将所有的旧数据进行迁移,因此内存占用的大小最终其实还是一样的,影响不大。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Golang Map原理(底层结构、查找/新增/删除、扩缩容) 的相关文章

  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • GoLong的学习之路,进阶,Viper(yaml等配置文件的管理)

    本来有今天是继续接着上一章写微服务的 但是这几天有朋友说 再写Web框架的时候 遇到一个问题 就是很多的中间件 redis 微信 mysql mq 的配置信息写的太杂了 很不好管理 希望我能写一篇有管理配置文件的 所以这篇就放到今天写吧 微
  • C语言之变量的存储方式和生存周期

    一 变量的存储方式 C语言变量的存储有两种方式 静态存储方式和动态存储方式 相应的生产期也有两种 静态生存期和自动生存期 静态存储方式 在程序运行前为变量内存分配内存 在程序结束后回收变量的内存 静态生存期 动态存储方式 在程序运行过程中
  • 每日一练2023.12.17——大笨钟的心情【PTA】

    题目链接 L1 077 大笨钟的心情 题目要求 有网友问 未来还会有更多大笨钟题吗 笨钟回复说 看心情 本题就请你替大笨钟写一个程序 根据心情自动输出回答 输入格式 输入在一行中给出 24 个 0 100 区间内的整数 依次代表大笨钟在一天
  • 深度学习目标检测全连接层什么意思

    在深度学习目标检测中 通常我们使用卷积神经网络 Convolutional Neural Network CNN 进行特征提取 CNN 的主要结构包括卷积层和池化层 用于从输入图像中提取特征 然而 为了最终输出目标的类别和位置信息 通常在网
  • leetcode 560. 和为 K 的子数组(优质解法)

    代码 class Solution public int subarraySum int nums int k int length nums length key 表示前缀和 value 表示个数 HashMap
  • 数组对象排序 (arr.sort())

    前端面试题库 面试必备 推荐 地址 前端面试题库 对象排序 arr sort 描述 方法sort 将在原数组上对 数组元素 进行排序 即排序时不创建新的数组副本 如果想按照别的顺序进行排序 就必须提供比较函数 该函数要比较两个值 然后返回一
  • 数据结构算法-快速排序

    核心思路 快速排序算法核心思路 选择一个 基准 元素 将数组分为两个子数组 一个包含比基准小的元素 另一个包含比基准大的元素 然后对这两个子数组进行递归排序 基准数 初始化两个索引 i 和 j 分别子数组的开头和结尾 初始化基准元素 bas
  • 面试150-13(Leetcode238除自身以外数组的乘积)

    代码 class Solution public int productExceptSelf int nums int n nums length int res new int n int product 1 int zerocnt 0
  • LeetCode21. Merge Two Sorted Lists

    文章目录 一 题目 二 题解 一 题目 You are given the heads of two sorted linked lists list1 and list2 Merge the two lists into one sort
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • Leetcode 45 跳跃游戏 II

    题意理解 给定一个长度为 n 的 0 索引 整数数组 nums 初始位置为 nums 0 每个元素 nums i 表示从索引 i 向前跳转的最大长度 还是从初始坐标i 0的位置到达最后一个元素 但是问题不是能不能跳到 而是 最少几步能跳到最
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • 【每日一题】2397. 被列覆盖的最多行数-2024.1.4

    题目 2397 被列覆盖的最多行数 给你一个下标从 0 开始 大小为 m x n 的二进制矩阵 matrix 另给你一个整数 numSelect 表示你必须从 matrix 中选择的 不同 列的数量 如果一行中所有的 1 都被你选中的列所覆
  • 搜索二叉树(BSTree)

    一 搜索二叉树的概念 二叉搜索树又称为做二叉排序树 二叉查找树 其要么是一棵空树 要么是一个满足以下性质的二叉树 若它的左子树不空 则左子树上所有结点的关键字均小于根结点关键字 若它的右子树不空 则右子树上所有结点的关键字均大于根结点关键字
  • Go、Docker、云原生学习笔记全攻略:从零开始,一步步走向精通!(2024版)

    第一章 Go语言学习宝典 一 介绍 01 Go 语言的前生今世 二 开发环境搭建 01 Go 语言开发环境搭建 三 初识GO语言 01 Go 多版本管理工具 02 第一个 Go 程序 hello world 与 main 函数 03 Go
  • 链表的中间节点

    链表的中间节点 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 备战技术面试 力扣提供海量技术面试资源 帮助你高效提升编程技能 轻松拿下世界 IT 名企 Dream Offer https leetcode cn problems
  • 单向不带头链表的使用

    单向不带头链表的使用 链表的创建 typedef struct LNode SLDataType data struct LNode next LNode LinkList 按位查找 LNode GetElem LinkList L int
  • 「优选算法刷题」:移动零

    嗨 这个假期罗根开始接触了算法 在为今年的蓝桥杯做准备 所以 开个新专栏 记录记录自己做算法题时的心得 一 题目 给定一个数组 nums 编写一个函数将所有 0 移动到数组的末尾 同时保持非零元素的相对顺序 请注意 必须在不复制数组的情况下
  • 【go语言】AST抽象语法树详解&实践之扫描代码生成错误码文档

    背景 为了能识别出代码中抛出错误码的地址和具体的错误码值 再根据错误码文件获取到错误码的具体值和注释 方便后续的排错 这里使用AST进行语法分析获取到代码中的目标对象 一 编译过程 在开始解析代码之前先补充了解一下编译过程 编译过程是将高级

随机推荐

  • Mysql 5.7 / 5.8 性能测试

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 转载于 https my oschina net u 582827 blog 1802981
  • Python训练了个模型,怎么交给Java用呢?

    最近碰到几个人问 如何实现 java 调用他们写好的 Python 应用 模型 这里我就把几种常见的办法做下汇总整理 喜欢本文记得收藏 关注 点赞 注 文末提供技术交流群 推荐文章 李宏毅 机器学习 国语课程 2022 来了 有人把吴恩达老
  • 【应用层】DNS协议

    一 概述 本篇文章基于 计算机网络 和 计算机网络 自顶向下方法 为笔者的读书笔记 主要内容如下所示 DNS提供的服务 互联网的域名结构 DNS服务器的分布 DNS的工作原理 DNS记录 往DNS插入记录 二 DNS提供的服务 域名系统 D
  • (二十九)admin-boot项目之自定义全局拦截404异常

    二十九 自定义全局拦截404异常 项目地址 https gitee com springzb admin boot 如果觉得不错 给个 star 简介 这是一个基础的企业级基础后端脚手架项目 主要由springboot为基础搭建 后期整合一
  • 深入理解Linux内核(第三版)- 进程切换

    为了控制进程的执行 内核必须有能力挂起正在CPU上运行的进程 并恢复以前挂起的某个进程的执行 这种行为被称为进程切换 process switch 任务切换 task switch 或上下文切换 context switch 硬件上下文 尽
  • 5G信令流程详解-45G互操作流程详解

    5G信令流程详解 45G互操作流程详解
  • java打开本机应用程序_Java启动本机应用程序EXE的三种方式

    Java代码 第一种方式 利用cmd方式 执行cmd命令 param command throws IOException public static String executeCmd String command throws IOEx
  • 常见报错01

    1 apt get no process found 如果你在使用apt get命令时出现 apt get no process found 错误消息 可能是以下原因之一 1 没有正在运行的apt get进程 这个错误通常会在你尝试终止一个
  • Python中的for循环和range()函数用法详解

    引言 在Python编程中 for循环和range 函数是非常常用的语法结构 用于遍历序列和重复执行一段代码块 本文将详细介绍Python中for循环和range 函数的用法 包括语法 参数 应用场景 并结合实际案例进行分析 一 for循环
  • maven 打包 releases 和 snapshots 版本

    releases 线上版本 生产环境使用的 snapshots 快照版本 开发过程中使用的 maven 打包代码到私服根据version 后面是否带有 SNAPSHOTS 来区分是打包线上版本还是快照版本 如果带有 SNAPSHOTS 打包
  • webpack + TypeScript搭建工程

    工程搭建 环境 浏览器 模块化 webpack 构建工具 更据人口文件找寻依赖关系 打包 安装 npm i webpack webpack cli D 安装插件 npm i D html webpack plugin clean webpa
  • Ubuntu18.04安装搜狗拼音输入法后无法输入中文

    系统环境 Ubuntu18 04 6 LTS 安装命令 sudo dpkg i sogoupinyin 4 2 1 145 amd64 deb 安装后显示输入法输入框 并不能输入中文 只能输入英文 解决办法 sudo apt get ins
  • 10个好用又有趣的工具类网站,赶快收藏吧

    YwTools 工具集合 www ywcoding com YwTools是一个提供许多有趣小工具的网站 这些工具能够为用户提供方便 快捷的支持 它提供许多实用性工具 比如生产力工具 免注册流程图 文本对比去重 编程类工具比如文本解码编码
  • 使用Appuploader工具将IPA上传到App Store的最新流程和步骤

    苹果官方提供的工具xcode上架ipa非常复杂麻烦 用appuploader 可以在 mac 和windows 上制作管理 证书 无需钥匙串工具 条件 1 以Windows为例 创建app打包ios需要的证书和描述文件 2 准备好一个苹果开
  • Xilinx FIFO IP核的例化和使用(含代码实例)

    使用FPGA进行数据传输处理时 数据缓存是很关键的部分 FIFO作为一种简单的缓存方案 在FPGA开发中具有广泛的应用 Xilinx为我们提供的FIFO IP核是一种先进先出 FIFO 内存队列 例化后 开发人员可自定义宽度 深度 状态标志
  • Android Menu详解

    菜单的分类 菜单是Android应用中非常重要且常见的组成部分 主要可以分为三类 选项菜单 上下文菜单 上下文操作模式以及弹出菜单 它们的主要区别如下 1 选项菜单是一个应用的主菜单项 用于放置对应用产生全局影响的操作 如搜索 设置 2 上
  • 随机采样方法整理与讲解(MCMC、Gibbs Sampling等)

    本文是对参考资料中多篇关于sampling的内容进行总结 搬运 方便以后自己翻阅 其实参考资料中的资料写的比我好 大家可以看一下 好东西多分享 PRML的第11章也是sampling 有时间后面写到PRML的笔记中去 背景 随机模拟也可以叫
  • 解决VS Code集成终端中Node命令不可用的问题

    问题 VS Code集成终端中输入node v提示 node 不是内部命令 解决方法 1 右键点击VS Code启动图标 选择属性 2 点击兼容性选项卡 3 勾选以管理员身份运行 4 打开VS Code集成终端 输入node v 成功
  • html5 js获取设备信息,js怎么获取电脑硬件信息

    想知道怎么获取电脑的硬件信息吗 下面是学习啦小编带来js怎么获取电脑硬件信息的内容 欢迎阅读 js怎么获取电脑硬件信息 1 写一个js获取userAgent属性的html文件 文件内容如下 alert window navigator us
  • Golang Map原理(底层结构、查找/新增/删除、扩缩容)

    参考 解剖Go语言map底层实现 Go语言核心手册 3 字典 一 Go Map底层结构 Go map的底层实现是一个哈希表 数组 链表 使用拉链法消除哈希冲突 因此实现map的过程实际上就是实现哈希表的过程 先来看下go map底层的具体结