go语言map底层及扩容机制原理详解(上)

go语言DAY7 字典Map 指针 结构体 函数 这样就把所有散列的map元素按哈希值与队列数的模值这样的规律放好,将来想找到某一个map元素,就根据模值找到这个元素所在的队列,在根据这个元素的哈希值具体查找到这个元素,这样的话效率对比查找某个map值需要遍历整个map集合来说效率就高了3/4。哈希值与B的&运算,但是B值与哈希值相比较高位全部为0,高位&运算的二进制结果都为0,没有意义,通常取哈希值低B位进行运算得到对应标准桶的索引下标。也不能是嵌套的切片,map类型。bmap代表的是桶数组中的桶对象,bmap负责存储key,value值。 阅读详情

底层数据结构-哈希表

go语言map的底层数据结构是哈希表:通过哈希表来存储键值对,通过hash函数把键值对散列到一个个桶(bucket)中。

什么是哈希表?

  • 在顺序结构以及平衡树中,元素与其的存储位置之间没有对应关系,因此查找一个元素时,必须进行多次比较。所以顺序查找的时间复杂度为O(N),而平衡树中则为树的高度O(log2N)。
  • 为了减少搜素时元素比较的次数,是否有一种方法可以不经过任何比较,通过元素的存储位置与它的关键码以O(1)的时间复杂度直接找到该元素呢?
  • 哈希表就是通过某种函数(hash)来使元素的存储位置和其元素值之间建立一一映射的关系,那么就可以通过这种关系快速找到该元素。(数组就是一种简单的哈希表)

如何处理哈希冲突?

  • 当两个或多个健具有相同的哈希值,即为出现了哈希冲突,它们会被存放在同一个桶中。go采用拉链法来解决哈希冲突的问题,即在同一个桶内部通过链接(链表)存储所有冲突的键值对。
  • 不过拉链法在当哈希冲突出现的次数相当频繁时,会将常数级的时间复杂度上升甚至到线性级。加载因子的出现就是为了避免过多的哈希冲突导致哈希表的退化。

无序性

  • 由于go语言的map是通过哈希表来实现的,由于哈希函数的特性,是无法依据一定的顺序来存储的。因此go的map是无序的。

map的扩容机制

在哈希表中,当元素达到一定的数量(超过加载因子设定的比例),为了保持操作的效率,需要对哈希表进行扩容。扩容通常需要创建一个更大的哈希表,并将现有元素重新映射到新表中。

底层实现


type hmap struct {
	count     int    // 元素的个数
	B         uint8  // buckets 数组的长度就是 2^B 个
	overflow uint16 // 溢出桶的数量
 
	buckets    unsafe.Pointer // 2^B个桶对应的数组指针
	oldbuckets unsafe.Pointer  // 发生扩容时,记录扩容前的buckets数组指针
 
	extra *mapextra //用于保存溢出桶的地址
}

type mapextra struct {
	overflow    *[]*bmap
	oldoverflow *[]*bmap
 
	nextOverflow *bmap
}

type bmap struct {
	tophash [bucketCnt]uint8
}
 
//在编译期间会产生新的结构体
type bmap struct {
    tophash [8]uint8 //存储哈希值的高8位
    data    byte[1]  //key value数据:key/key/key/.../value/value/value...
    overflow *bmap   //溢出bucket的地址
}

在go的map实现中,它的底层结构体是hmap,hmap里维护着若干个bucket数组 (即桶数组)。每个桶中保存了8个键值对,如果8个满了,又来了一个kv到了这个桶中,会使用overflow连接下一个桶,即桶溢出。

  • 对于哈希冲突:当两个不同的key落到了同一个桶中就是发生了哈希冲突,则会采用拉链法,从前往后找一个空位进行插入。如果桶满了,当前桶就会连接到下一个溢出桶。

扩容基本步骤

  1. 触发扩容:
    • 当向map中添加新元素时,如果元素数量超过了当前哈希表容量和加载因子的乘积,就会触发扩容。加载因子是一个决定性能与内存使用之间的阈值,防止哈希表的退化。
  2. 分配新表
    • go在运行是会创建一个新的哈希表,其容量为原来的两倍。这样做可以减少再次扩容的可能,并提供足够的空间来避免过多的哈希冲突。
  3. 数据迁移
    • 将旧哈希表中的现有元素迁移到新表中。每个元素的哈希中将根据新表的大小容量重新计算,来确定它们在新表的位置。
    • 当map非常大的情况下,每次迁移所有的元素,会出现长时间的暂停。在go1.8版本之后,这个步骤是渐进式的:每次向map`添加新元素或查找时,都会迁移一小部分元素,避免长时间的暂停。
  4. 更新引用
    • 当所有元素都迁移到新的哈希表中后,原来的哈希表将会被丢弃,map的内部引用将指向新表。

总结

  1. 要提供合适的初始容量。
    由于每次扩容时,需要重新计算所有元素的哈希值并将它们分配到新的桶中,这是一个相当花时间的操作。因此,如果我们事先知道map大约会存储多少数据,可以实现在创建map时通过提供合适的初始容量来减少扩容次数,从而提高map的性能:
    myMap := make(map[string]int, initialCapacity)
Golang Map 深度剖析:原理、实践与面试要点 嘿,小伙伴们!我是 k 哥。今天,咱们来聊聊 Map。在 Go 语言这个神奇的世界里,Map 这个有点神秘的数据结构一直都是开发者们特别关注的。你是不是在用 Map 的时候,对它里面咋工作的感到好奇?是不是碰到复杂操作的时候,特别想弄明白它背后的原理?别着急,今天这篇文章就带你走进 Go 语言 Map 那个神秘的世界,帮你一层一层揭开它的面纱!从底层的原理,到最佳实践,再到高频面试题的分析,这篇文章会从各个方面满足你的求知心。 阅读详情

相关推荐

GD32F303 ADC+DMA实战:用硬件过采样搞定多通道数据采集(附完整代码)

本文详细介绍了GD32F303系列MCU通过ADC+DMA结合硬件过采样技术实现多通道数据采集的实战方案。从硬件设计要点到软件配置全解析,包括时钟树配置、ADC模块初始化和DMA传输设置,提供了完整的代码示例和优化技巧,帮助工程师构建高精度、低功耗的数据采集系统。

weixin_30466039的博客 317

GO语言面试精华——map 的扩容过程是怎样的?

这样的循环在 map 的源码里到处都是,要理解透了。对于条件 2 的解决方案,曹大的博客里还提出了一个极端的情况:如果插入 map 的 key 哈希都一样,就会落到同一个 bucket 里,超过 8 个就会产生 overflow bucket,结果也会造成 overflow bucket 数过多。对于条件 2,从老的 buckets 搬迁到新的 buckets,由于 bucktes 数量不变,因此可以按序号来搬,比如原来在 0 号 bucktes,到新的地方后,仍然放在 0 号 buckets。

白话机器学习 419

5款免费二进制编辑器横向评测:从HxD到ImHex谁更适合你?

本文对5款免费二进制编辑软件进行深度横向评测,包括HxD、wxHexEditor、010 Editor、ImHex和GNU Emacs,涵盖从轻量级到专业级的各种需求。评测基于文件处理能力、编辑功能、分析工具等核心维度,为开发者、逆向工程师和安全研究人员提供实用选择指南,帮助用户根据具体场景选择最适合的二进制编辑工具。

weixin_29278805的博客 371

Go最全HashMap实现原理, 扩容机制,面试题和总结_hashmap扩容机制面试,Golang开发面试基础

【代码】Go最全HashMap实现原理, 扩容机制,面试题和总结_hashmap扩容机制面试,Golang开发面试基础。

2401_84910880的博客 723

电子取证之服务器取证思路

服务器取证是一个技术性很强的领域,涉及到多个环节,包括网站重构、网站代码分析、容器分析等。数据库密码的获取通常通过查看配置文件、日志文件、备份等途径,同时可以通过一些绕过技术如禁用身份验证来获取权限。对于不同的数据库,需要根据其特性采取相应的应对方法和命令。

2401_84830066的博客 2484

Unity游戏开发客户端面经——设计模式(初级)

Unity游戏开发客户端面经 之 设计模式 篇

Sea3752的博客 9425

Go语言中Map的自动扩容机制

哈希表是用空间换时间的结构,但是在扩容是会非常耗时,因此在对时间性能有要求的情况下,应该预估map设定的大小,尽量避免扩容操作。的扩容操作,扩容操作会重新分配更大的哈希表,将原有的键值对重新哈希到新的哈希表中。在触发扩容后,Go 会创建一个新的哈希表,其大小默认是当前哈希表大小的两倍。中的元素数量达到当前哈希表大小的 6.5 倍时,Go 会触发扩容操作。遍历原有的哈希表,将每个键值对重新计算哈希值,并插入到新的哈希表中。扩容完成后,释放原有的哈希表内存空间,将新的哈希表作为。扩容完成后,可以继续往。

weixin_73075352的博客 722

STM32电机库(ST-MC-Workbench)实战指南——三电阻电流采样电路参数配置详解

本文详细解析了STM32电机库(ST-MC-Workbench)中三电阻电流采样电路的参数配置方法,涵盖电阻选型、放大增益计算、滤波时间优化等关键步骤。通过实战案例和调试技巧,帮助工程师快速掌握电流采样技术,提升电机控制系统的精度与稳定性。特别针对ST-MC-Workbench界面参数设置提供了详细指导。

weixin_29186249的博客 505

go语言的map

go-map

weixin_44913757的博客 999

FPGA实战:MIG IP核AXI接口DDR3读写优化指南

本文详细解析了FPGA中MIG IP核AXI接口的DDR3读写优化策略,涵盖关键参数配置、突发传输技巧及时序收敛方案。通过AXI接口优化和突发长度选择,可实现高达12.8GB/s的理论带宽,提升DDR3访问效率。文章还提供了Vivado配置实例和调试技巧,助力开发者解决FPGA内存控制中的实际问题。

weixin_29168393的博客 545

从报错到定位:Memtest86 DQ位算法解析与实践指南

本文深入解析了Memtest86内存测试工具报错信息的解读方法,重点介绍了通过地址解析、数据转换、位比对和DQ定位四步算法,精准锁定故障DQ位的实践流程。文章以DDR4和DDR5为例,详细拆解了从十六进制报错到二进制比对的完整步骤,并提供了从单个报错分析到故障模式诊断的进阶指南,帮助用户将抽象的报错信息转化为具体的维修或排查依据。

sre5engineer的博客 486

Go map的底层原理(存储、扩容)

Go map的底层原理 map的实现原理 map的底层结构 map的扩容机制 map的实现原理 数组+链表、拉链法 map的底层结构 hmap 哈希表 hmap是Go map的底层实现,每个hmap内都含有多个bmap(buckets桶、oldbuckets旧桶、overflow溢出桶),既每个哈希表都由多个桶组成。 type hmap struct { count int //元素的个数 flags uint8 //状态标志 B u

weixin_45743893的博客 5231

手把手教你用组态王KingSCADA 3.53读取S7-1200 PLC数据(保姆级图文教程)

本文提供了一份详细的组态王KingSCADA 3.53读取S7-1200 PLC数据的实战教程,涵盖从硬件连接到软件配置、变量映射到故障排查的全流程。特别针对初学者常见问题提供解决方案,帮助工程师快速实现稳定通信,提升工业自动化系统效率。

weixin_26876073的博客 671

go语言map底层及扩容机制原理详解(下)

上文对Go map的底层数据结构有所了解,并对其扩容机制的步骤进行简略的描述。本文将会详细地去解释Go map扩容机制的详细原理。

codekingo的博客 367

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Go语言map的底层原理

hmap结构hmap是map的顶层结构,包含了管理map操作所需要的各类元数据。(比如哈希种子,桶的数量,桶的地址)其次hmap通过buckets字段(指针类型)引用了所有的bucket。buckets是一个指向bucket数组的指针,数组中的每一个元素就是一个bmap。bmap结构bamp是实际存储键值对的结构。每一个bamp可以存储多个键值对,并且包含溢出指针,用来处理哈希冲突的情况。bmap结构内的数组包含了多个键值对槽位 (key/val。

2303_79710813的博客 1578

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

关于Go语言的底层,Slice,map

Go map 默认是并发不安全的,同时对 map 进行并发读写的时,程序会 panic,原因如下:Go 官方经过长时间的讨论,认为 map 适配的场景应该是简单的(不需要从多个 gorountine 中进行安全访问的),而不是为了小部分情况(并发访问),导致大部分程序付出锁的代价,因此决定了不支持。map 在扩缩容时,需要进行数据迁移,迁移的过程并没有采用锁机制防止并发操作,而是会对某个标识位标记为 1,表示此时正在迁移数据。

viviiuw的博客 1023

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Golang Map简介

map_var := make(map[K]V) // 用make函数创建一个空的map,其中K和V分别为键和值的类型map_var[key] = value // 向map中添加一个键值对value := map_var[key] // 获取指定键的值delete(map_var, key) // 从map中删除指定的键及其对应的值本节我们学习了Go语言中的map数据类型,使用方法以及map的数据安全问题。

fronge的博客 1228

【go】go语言slice/map的产生背景,及原理理解

先讲一下slice的产生背景,首先,go本身是有数组的,但是不会自动扩容,然而实际工作中会有很多场景是要求能自动扩容的,比如说你接用户的数据,你肯定要设置一个可以自动扩容的数组来接,因为你没办法预测他会传多大的数据,提前设置的数组过大或者过小都不合适,因此,go的slice应运而生slice结构包含一个指向底层数组的指针长度容量。这样的设置就可以看出他是一个轻量级的容器,因为实际上只是一个对底层数组的引用。slice的扩容机制,在长度超过容量的时候,如果容量<1024那么双倍。

m0_74282926的博客 1642

Go语言入门之Map详解

name:变量名key_type:键的类型value_type:值的类型// 方式一// 方式二1 : "老一",2 : "老二",3 : "老三",// 方式三:5代表容量,也就是在内存中占用多大的空间,可以省略。

clisks的博客 1596

Go map 底层,以及扩容

总结笔记《Go语言精进之路》

小目标 1294

golang map原理

先说结论,在 golang 中 map 是无序的,准确的说是无法严格保证顺序的, 从上面的源码中我们可以知道,golang 中 map 在扩容后,可能会将部分 key 移至新内存,由于在扩容搬移数据过程中,并未记录原数据位置, 并且在 golang 的数据结构中也并未保存数据的顺序,所以那么这一部分在扩容后实际上就已经是无序的了。不难看出,即使不对 map 进行扩容,在多次遍历时也是无序的,这是因为 golang 官方在设计时故意加上随机的元素,将遍历 map 的顺序随机化,用来防止使用者用来顺序遍历。

阿拉斯加大闸蟹的博客 1万+

GoLang之map底层系列三(书面版)

GoLang之map底层系列二(深度学习)

weixin_52690231的博客 2693

12 Go语言map底层浅析

Go语言map底层浅析 文章目录Go语言map底层浅析1、Hash函数2、map 源码2.1hmap(a header of map)2.2 bmap(a bucket of map)3、hmap和bmap结构图4、map的扩容5、结语 笼统的来说,go的map底层是一个hash表,通过键值对进行映射。 键通过哈希函数生成哈希值,然后go底层的map数据结构就存储相应的hash值,进行索引,最终...

小人物、平凡,世界很大我只想在家 509

Go【Map】进阶篇:底层原理、赋值、访问、扩容流程

Go语言中map的底层实现采用哈希表结构,主要由hmap和bmap组成。hmap是map的核心控制结构,包含桶数组、哈希种子等元信息;bmap是实际存储键值对的桶结构,每个桶可存8个键值对,采用紧凑的内存布局存储tophash、keys和values数组,并通过overflow指针连接溢出桶。map初始化时会根据容量计算桶数量,写入数据时通过哈希值定位桶位置,并处理可能的扩容迁移。这种设计在保证高效查找的同时,通过溢出桶减少了扩容频率。

qq_49232751的博客 1139
上一篇: 详解Go语言Map
下一篇: 详解动态规划之01背包问题及其空间压缩(图文并茂+例题讲解)
看繁星aa
博客等级 码龄4年 922粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值