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

server/2024/9/24 7:12:01/

底层数据结构-哈希表

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    *[]*bmapoldoverflow *[]*bmapnextOverflow *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)

http://www.ppmy.cn/server/40990.html

相关文章

信创应用软件之邮箱

信创应用软件之邮箱 文章目录 信创应用软件之邮箱采用信创邮箱的必要性信创邮箱采购需求国产邮箱业务形态国产邮箱代表性品牌CoremailRichmail安宁eyouUMail拓波 邮件安全的发展阶段 采用信创邮箱的必要性 邮箱是天然的数据存储空间,党政和央国企客户在使用过程中存…

ubuntu server 22.04 安装docker、docker-compose

ubuntu server 22.04安装docker有两种方式,第一种是使用ubuntu镜像源的软件包进行安装,第二种使用官方GPG密钥手动添加Docker存储库方式进行安装,两种方式都可以,但第二种方式略复杂,这里介绍第一种比较简单的安装方式…

redis试题按知识点归类(三)

十一、发布/订阅 1.Redis 的发布/订阅模型是如何工作的? 2.如何使用 Redis 作为消息队列? 3.发布/订阅在实际应用中有哪些用例? 十二、缓存策略 1.如何确定 Redis 的缓存策略? 2.Redis 的缓存替换策略有哪些? 3…

鸿蒙ArkUI-X跨平台开发电商应用

一、ArkUI-X 简介 ArkUI-X 是由 OpenHarmony TSC - 跨平台应用开发框架 TSG 所孵化的开源项目,使用ArkUI-X可以让开发者基于一套主代码, 就可以构建支持多平台的精美、高性能应用。目前支持OpenHarmony、HarmonyOS、Android、 iOS,后续会逐步增加更多平台支持。 ArKUI跨平台…

每日两题 / 138. 随机链表的复制 148. 排序链表(LeetCode热题100)

138. 随机链表的复制 - 力扣(LeetCode) 用哈希表记录原链表中的节点是否被复制过 遍历原链表并通过哈希表维护新链表 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node(int _val) {val _val;next NULL;rand…

web前端学习笔记9

9. HTML5新增元素及属性 9.1 HTML5新增结构元素 HTML5引入了几个新的结构元素,极大地改善了网页的组织和结构方式。以下是HTML5中的一些关键新结构元素: 标签说明<header>页面或页面中某一个区块的页眉,通常是一些引导和导航信息<nav>可以作为页面导航的链接组&…

信息系统安全选择题 1-10章 汇总【太原理工大学】

第一章 1.信息未经授权不能改变的安全特性称为_D b.有效性 a.保密性 d.完整性 c.可控性 2.信息系统防止信息非法泄露的安全特性称为_D b.有效性 a.完整性 d.保密性 c.可控性 6.《国际通用信息安全评价标准ISO/IEC 15408》在安全保证要求中定义了7个评价保证等级&…

M 有效算法

M 有效算法 本题考验二分知识&#xff0c;思路是二分k的取值&#xff0c;就按第一组样例来说当我们k取值为1的时候我们遍历数组想让|8-x|<k1的话x的取值范围是7-9&#xff0c;想让|3-x|<k2的话x的取值范围是1-5&#xff0c;两者x的区间不重合&#xff0c;说明肯定没有x能…