当前位置: 首页 > news >正文

10 go语言(golang) - 数据类型:哈希表(map)及原理(二)

扩容

在 Go 语言中,当 map 的元素数量达到一定阈值时,会触发扩容操作以保持性能。这个过程称为 rehashing,即重新散列所有的键值对到一个更大的哈希表中。

扩容的条件

源码:

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {// 省略代码...// Did not find mapping for key. Allocate new cell & add entry.// If we hit the max load factor or we have too many overflow buckets,// and we're not already in the middle of growing, start growing.if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {hashGrow(t, h)goto again // Growing the table invalidates everything, so try again}// 省略代码...
}

mapassign 函数会在以下两种情况发生时触发哈希的扩容:

  1. 负载因子已经超过 6.5;
  2. 哈希使用了太多溢出桶;

不过因为 Go 语言哈希的扩容不是一个原子的过程,所以还需要判断当前哈希是否已经处于扩容状态,避免二次扩容造成混乱,即:!h.growing()

负载因子

默认负载因子:Go 的 map 通常会在平均每个桶有超过6.5个元素时触发扩容。

也就是说当 map中数据总个数 / 桶个数 > 6.5 ,引发翻倍扩容。而6.5如何来的?看源码:

	// Maximum average load of a bucket that triggers growth is bucketCnt*13/16 (about 80% full)// Because of minimum alignment rules, bucketCnt is known to be at least 8.// Represent as loadFactorNum/loadFactorDen, to allow integer math.loadFactorDen = 2loadFactorNum = loadFactorDen * abi.MapBucketCount * 13 / 16
  1. bucketCnt(abi.MapBucketCount)

    • bucketCnt 是每个桶(bucket)中可以存储的键值对数量。

    • 在Go中,这个值通常是8。源码:

      	// Maximum number of key/elem pairs a bucket can hold.MapBucketCountBits = 3 // log2 of number of elements in a bucket.MapBucketCount     = 1 << MapBucketCountBits // 1左移三位就是二进制的1000 ,就是十进制的8
      
  2. 负载因子(Load Factor)

    • 负载因子决定了在何时需要对 map 进行扩容。
    • 最大平均负载被设定为桶容量的80%(即每个桶大约80%满时会触发扩容)。
  3. loadFactorNum 和 loadFactorDen

    • 使用分数形式来表示负载因子,以便使用整数运算而不是浮点运算,这样可以提高计算效率和精度。
    • loadFactorDen = 2 是分母,用于简化整数运算。
    • loadFactorNum = loadFactorDen * abi.MapBucketCount * 13 / 16 是分子。

解读源码可以知道:最大负载因子被设置为 loadFactorNum/loadFactorDen = 13/2 = 6.5

扩容的方式

扩容方法hashGrow的源码:

func hashGrow(t *maptype, h *hmap) {// If we've hit the load factor, get bigger.// Otherwise, there are too many overflow buckets,// so keep the same number of buckets and "grow" laterally.bigger := uint8(1)if !overLoadFactor(h.count+1, h.B) {bigger = 0h.flags |= sameSizeGrow}oldbuckets := h.bucketsnewbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)...// commit the grow (atomic wrt gc)h.B += bigger // 如果不是超过负载因子触发的扩容,则bigger=0,即B不变,容量不变,属于等量扩容...
}

map的扩容机制如下:

  • 增量扩容:当负载因子超过阈值时,map会进行增量扩容。在这种情况下,新创建的哈希表的大小是旧哈希表的两倍,即h.B会增加1,表示桶的数量翻倍。这种扩容方式称为增量扩容,因为每次只会扩大一倍,而不是无限制地增加桶的数量。
  • 等量扩容:当溢出桶的数量过多,但负载因子没有超过阈值时,map会进行等量扩容。这种情况下,新创建的哈希表的大小与旧哈希表相同,即h.B不会增加,桶的数量保持不变。这种扩容方式实际上是对现有数据的重新分布,目的是减少溢出桶的数量,使数据分布更加均匀,提高map的性能。

具体步骤

1、检测扩容条件

每次向 map 中插入新元素后,都会检查是否要触发扩容。

  • 负载因子:当负载因子超过6.5时,会触发扩容。
  • 溢出桶:如果溢出桶数量过多,也会进行扩容。

2、分配新桶数组

  • 增量扩容时:会为map分配一个新的、更大的bucket数组。新数组大小是当前大小的两倍(即B增加1,使得bucket数量变为原来的两倍)。
  • 等量扩容时:新旧bucket数组大小保持不变,但元素会被重新分布以减少溢出。

3、初始化迁移状态

  • 将旧bucket数组指针保存到oldbuckets字段。
  • buckets指向新创建的桶(新桶中暂时还没有数据)
  • 更新map结构中的其他相关字段以支持增量迁移。
func hashGrow(t *maptype, h *hmap) {...// commit the grow (atomic wrt gc)h.B += biggerh.flags = flagsh.oldbuckets = oldbuckets // 原本的桶设置为oldbucketsh.buckets = newbuckets // 把新的桶设置为当前bucketsh.nevacuate = 0 // nevacuate记录已经完成搬移的bucket数量,初始化为0表示从第一个bucket开始进行增量迁移。h.noverflow = 0 // 扩容后,新桶中的溢出桶计数器被重置,因为新结构中尚未使用任何溢出桶。if h.extra != nil && h.extra.overflow != nil {// Promote current overflow buckets to the old generation.if h.extra.oldoverflow != nil {throw("oldoverflow is not nil")}h.extra.oldoverflow = h.extra.overflow // 将原来使用过的所有溢出桶引用保存到extra.oldoverflow, 这样可以在需要时访问这些老的数据结构以完成迁移操作。h.extra.overflow = nil // 新结构中还没有使用任何溢出,因此将其设为空,以便后续操作可以正确地开始分配新的溢出空间。}if nextOverflow != nil {if h.extra == nil {h.extra = new(mapextra)}h.extra.nextOverflow = nextOverflow // 指定新创建结构中的下一个可用溢出位置,这样当需要分配新的溢出空间时,可以快速找到起始点并进行管理。这有助于高效地处理未来可能出现的新冲突情况,并确保内存资源得到合理利用。}// the actual copying of the hash table data is done incrementally// by growWork() and evacuate().
}

4、迁移数据

每次对map进行插入、删除或查找操作时,都会调用evacuate函数来逐步搬移旧桶中的元素到新桶中。

func growWork(t *maptype, h *hmap, bucket uintptr) {// make sure we evacuate the oldbucket corresponding// to the bucket we're about to use// 接下来的操作是为了确保将要使用的bucket对应的旧bucket已经被搬空。evacuate(t, h, bucket&h.oldbucketmask())// bucket & h.oldbucketmask() 计算出当前访问的新桶对应的旧桶编号。// 调用evacuate函数来处理这个旧桶,将其元素搬到新结构中。// evacuate one more oldbucket to make progress on growing// 接下来会额外再处理一个旧桶,以推进整个扩容过程。if h.growing() {evacuate(t, h, h.nevacuate)}
}

5、搬移逻辑

  • **增量扩容:**对于每个需要搬移的旧桶,将其所有元素重新计算哈希并放置到新的对应位置上。
    • 在进行扩容时,因为 bucket 数量翻倍(假设从 N 变为 2N),原有每个 bucket 索引会映射到两个可能的索引上:oldbucketoldbucket + N

      • 假设原来有 8 个 buckets,则 oldbuckets 的索引范围是 [0,7]。在扩容后,buckets 数变为16,则每个 oldbucket 对应两个 new buckets:
        • Bucket 0 映射到 New Bucket 0 和 New Bucket 8
        • Bucket 1 映射到 New Bucket 1 和 New Bucket 9
    • 对于正在迁移中的每个 key, 再次计算其 hash 值,根据hash值的 底B位 来决定将此键值对分流道那个新桶中。

      • 例如,如果有 16 (即 2 4 2^4 24 ) 个 buckets,则需要最低的四位(B=4)来确定 bucket 索引。
    • B的值在原来的基础上已加1,也就意味着通过多1位来计算此键值对要分流到新桶位置:

      • 当新增的位的值为 0,则数据会迁移到与旧桶编号一致的位置 :oldbucket。
      • 当新增的位的值为 1,则数据会迁移到翻倍后对应编号位置 :oldbucket + N。
  • **等量扩容:**重新计算 Hash
    • 对于每个 key,使用相同或可能更新的 hash 函数再次计算其 hash 值。这可能涉及到使用新的随机种子 hash0 来确保更好地散列。
    • 由于 hash 函数或种子可能发生变化,即使桶数没有增加,key 的 hash 值也可能与之前不同。以确保旧桶数据能比之前更均匀的散列不同桶中

6、完成迁移

  • 当所有旧桶都被处理完毕后,即所有元素都已成功从旧结构移动到新结构(将原来指向旧 hashtable 的指针指向新 hashtable),此时可以释放旧bucket数组所占用的内存资源。

相关文章:

10 go语言(golang) - 数据类型:哈希表(map)及原理(二)

扩容 在 Go 语言中&#xff0c;当 map 的元素数量达到一定阈值时&#xff0c;会触发扩容操作以保持性能。这个过程称为 rehashing&#xff0c;即重新散列所有的键值对到一个更大的哈希表中。 扩容的条件 源码&#xff1a; func mapassign(t *maptype, h *hmap, key unsafe.…...

【论文解读】Med-BERT: 用于疾病预测的大规模结构化电子健康记录的预训练情境化嵌入

【论文解读】Med-BERT: 用于疾病预测的大规模结构化电子健康记录的预训练情境化嵌入 Med-BERT:pretrained contextualized embeddings on large-scale structured electronic health records for disease prediction ​ ​ 摘要:基于电子健康记录(EHR)的深度学习(DL)预…...

[POI2014] PTA-Little Bird(单调队列优化 DP)

luogu 传送门https://www.luogu.com.cn/problem/P3572 解题思路 先设 表示到 的最小劳累值。 很容易得出转移&#xff1a; 其中 由 和 的大小关系决定&#xff0c;并且 。 很显然&#xff0c;直接暴力是 的&#xff0c;会超时。 于是&#xff0c;考虑优化。 我们发现…...

【含开题报告+文档+PPT+源码】基于SpringBoot的体育馆管理系统的设计与实现

开题报告 近年来&#xff0c;随着人们生活水平的提高和健康意识的增强&#xff0c;体育馆作为提供体育锻和休闲娱乐的重要场所&#xff0c;其使用频率和管理难度也在不断增加。传统的体育馆管理模式通常依赖于人工记录和手动操作&#xff0c;不仅效率低下&#xff0c;而且容易…...

Vue3学习:vue组件中的图片路径问题

今天在做一个案例的时候&#xff0c;图片放在assets/images文件夹下&#xff0c;如下路径&#xff0c;其中的图片不能正常显示。 list: [{ id: 1, name: 欧拉公式啤酒杯, price: 30.00, src: ./assets/images/Euler.png},{ id: 2, name: 高斯分布马克杯, price: 40.00, src: ./…...

openCV基础-图像预处理Day26

图像预处理 ​ 在计算机视觉和图像处理领域&#xff0c;图像预处理是一个重要的步骤&#xff0c;它能够提高后续处理&#xff08;如特征提取、目标检测等&#xff09;的准确性和效率。OpenCV 提供了许多图像预处理的函数和方法&#xff0c;以下是一些常见的图像预处理操作&…...

给文件添加可读可写可执行权限

在Unix、Linux或类Unix操作系统中&#xff0c;你可以使用chmod命令来给文件添加可读、可写和可执行权限。权限通常分为三组&#xff1a;文件所有者&#xff08;owner&#xff09;、文件所属组&#xff08;group&#xff09;和其他用户&#xff08;others&#xff09;。每组都可…...

golang有序map

最近使用go开发排行榜的需求, 有些情况会用到有序map, 但是go竟然没有有序map的实现 本着自己动手丰衣足食的原则, 就自己实现了一个 原理 原理比较简单, 主要结合了container/list双向链表和map 使用双向链表存储key和value, 保证顺序, 使用map存储key和节点信息, 保证查找…...

【LangChain系列4】【Chain模块详解】

目录 前言一、LangChain1-1、介绍1-2、LangChain抽象出来的核心模块1-3、特点1-4、langchain解决的一些行业痛点1-5、安装 二、Chain模块2-1、介绍2-2、LLMChain2-3、Sequential Chain&#xff08;顺序链&#xff09;2-4、Router Chain 总结 前言 LangChain给自身的定位是&…...

51c嵌入式~IO合集1

我自己的原文哦~ https://blog.51cto.com/whaosoft/12383193 一、单片机通信数据接收解析方法 前阵子一朋友使用单片机与某外设进行通信时&#xff0c;外设返回的是一堆格式如下的数据&#xff1a; AA AA 04 80 02 00 02 7B AA AA 04 80 02 00 08 75 AA AA 04 80 02 00 9B E2…...

ETLCloud怎么样?深度解析其在数据管理中的表现

在BI或数据大屏等数据分析工具中&#xff0c;经常需要从多个业务系统中提取原始数据&#xff0c;然后对数据进行清洗、处理&#xff0c;以获取高质量、有效且干净的数据以供后续的BI进行数据统计和分析使用&#xff0c;从高质量的实现企业数据的价值变现。 然而&#xff0c;在…...

高频谐振功放电路

目录 集电极馈电电路 高频扼流圈的作用: 并联馈电回路 高频扼流圈作用 : 优缺点 对于并联的集电极馈电网络: 对于串联的集电极馈电网络: 神奇之处 基级馈电电路 自反偏压: 复合输出回路 天线回路 效率分析 总效率分析 互感如何改变工作状态 集电极馈电电路 馈电电路分…...

kafka如何获取 topic 主题的列表?

大家好&#xff0c;我是锋哥。今天分享关于【kafka如何获取 topic 主题的列表&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; kafka如何获取 topic 主题的列表&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在Kafka中&#xff0c;可以…...

全新大模型框架Haystack,搭建RAG pipeline

大家好&#xff0c;在AI应用开发的赛道上&#xff0c;目前Haystack以其开源框架的优势&#xff0c;成为LLM技术领域的一匹黑马&#xff0c;对现有竞争者构成挑战。本文将介绍Haystack的亮点优势&#xff0c;并分析它为何能在众多LLM框架中脱颖而出&#xff0c;通过RAG应用实例来…...

儿童孤独症专家分享:了解治疗与支持的专业帮助

在儿童的成长旅程中&#xff0c;每一步都充满了探索与发现。然而&#xff0c;对于患有孤独症的儿童来说&#xff0c;这段旅程往往伴随着更多的挑战与困难。孤独症&#xff0c;这个看似遥远的词汇&#xff0c;却深刻地影响着无数家庭的生活。作为儿童孤独症领域的专家&#xff0…...

初始JavaEE篇——多线程(7):定时器、CAS

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 定时器的使用 定时器的原理 模拟实现定时器 CAS 介绍 CAS的应用场景 解析 AtomicInteger 类 实现自旋锁 CAS的缺陷…...

高精度计算(乘)

引言 此篇是专栏信息学杂谈第八篇高精度计算&#xff08;乘&#xff09;&#xff0c;展示了关于C如何实现高精度乘法的代码 正文&#xff1a; 乘法进位 c[i j - 1] a[i] * b[j] x; //x为之前进位 x c[i j - 1] / 10; c[i j - 1] % 10;完整代码&#xff1a; #include …...

在vue中 如何实现跨域

跨域问题是Web开发中常见的挑战&#xff0c;那么如何解决跨域呢&#xff0c;我们一起来看看吧&#xff01; 跨域是什么&#xff1f; 跨域&#xff08;Cross-Origin&#xff09;是指网络请求从一个域名&#xff08;origin&#xff09;发起&#xff0c;而请求的目标资源位于另一…...

计算机考研,选择西安交通大学还是哈工大?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 经过全面分析&#xff0c;2025年考研西安交通大学和哈尔滨工业大学计算机专业的报考难度对比如下&#xff1a; 西安交通大学计算机专业 > 哈尔滨工业大学计算机专业 对于想要报考985高校计算机专业但核心目标是优…...

微积分复习笔记 Calculus Volume 1 - 4.4 The Mean Value Theorem

4.4 The Mean Value Theorem - Calculus Volume 1 | OpenStax...

C++初阶-list的底层

目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态

前言 在人工智能技术飞速发展的今天&#xff0c;深度学习与大模型技术已成为推动行业变革的核心驱动力&#xff0c;而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心&#xff0c;系统性地呈现了两部深度技术著作的精华&#xff1a;…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…...