解剖Go语言map底层实现

栏目: Go · 发布时间: 6年前

内容简介:Golang中hmap:

mapGo 语言中基础的数据结构,在日常的使用中经常被用到。但是它底层是如何实现的呢?

map 的整体结构图

Golang中 map 的底层实现是一个散列表,因此实现 map 的过程实际上就是实现散表的过程。在这个散列表中,主要出现的结构体有两个,一个叫 hmap ( a header for a go map ),一个叫 bucket 。这两种结构的样子分别如下所示:

hmap:

解剖Go语言map底层实现

图中有很多字段,但是便于理解 map 的架构,你只需要关心的只有一个,就是标红的字段:buckets数组。Golang的map中用于存储的结构是bucket数组。而bucket(即 bmap )的结构是怎样的呢?

bucket:

解剖Go语言map底层实现

相比于 hmap ,bucket的结构显得简单一些,标红的字段依然是“核心”,我们使用的 map 中的key和value就存储在这里。“高位哈希值”数组记录的是当前bucket中key相关的“索引”,稍后会详细叙述。还有一个字段是一个指向扩容后的bucket的指针,使得bucket会形成一个链表结构。例如下图:

解剖Go语言map底层实现

由此看出 hmapbucket 的关系是这样的:

解剖Go语言map底层实现

而bucket又是一个链表,所以,整体的结构应该是这样的:

解剖Go语言map底层实现

哈希表的特点是会有一个哈希函数,对你传来的key进行哈希运算,得到唯一的值,一般情况下都是一个数值。Golang的 map 中也有这么一个哈希函数,也会算出唯一的值,对于这个值的使用,Golang也是很有意思。

Golang把求得的值按照用途一分为二:高位和低位。

解剖Go语言map底层实现

如图所示,蓝色为高位,红色为低位。 然后低位用于寻找当前key属于 hmap 中的哪个bucket,而高位用于寻找bucket中的哪个key。上文中提到:bucket中有个属性字段是“高位哈希值”数组,这里存的就是蓝色的高位值,用来声明当前bucket中有哪些“key”,便于搜索查找。 需要特别指出的一点是:我们 map 中的key/value值都是存到同一个数组中的。数组中的顺序是这样的:

解剖Go语言map底层实现

并不是key0/value0/key1/value1的形式,这样做的好处是:在key和value的长度不同的时候,可以消除padding带来的空间浪费。

现在,我们可以得到Go语言 map 的整个的结构图了:

解剖Go语言map底层实现

以上,就是Go语言map的整体结构了。

map 的扩容

当以上的哈希表增长的时候,Go语言会将bucket数组的数量扩充一倍,产生一个新的bucket数组,并将旧数组的数据迁移至新数组。

加载因子

判断扩充的条件,就是哈希表中的 加载因子 (即loadFactor)。

加载因子 是一个阈值,一般表示为:散列包含的元素数 除以 位置总数。是一种“产生冲突机会”和“空间使用”的平衡与折中: 加载因子 越小,说明空间空置率高,空间使用率小,但是 加载因子 越大,说明空间利用率上去了,但是“产生冲突机会”高了。

每种哈希表的都会有一个 加载因子 ,数值超过 加载因子 就会为哈希表扩容。 Golang的 map加载因子 的公式是: map长度 / 2^B 阈值是 6.5 。其中 B 可以理解为已扩容的次数。

当Go的 map 长度增长到大于 加载因子 所需的 map 长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段 oldbucket 中。注意:并不是立刻把旧的数组中的元素转义到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中。

如下图所示:当扩容的时候,Go的 map 结构体中,会保存旧的数据,和新生成的数组

解剖Go语言map底层实现

上面部分代表旧的有数据的bucket,下面部分代表新生成的新的bucket。蓝色代表存有数据的bucket,橘黄色代表空的bucket。 扩容时 map 并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移,如下图:

解剖Go语言map底层实现

注意:这里并不会直接删除旧的bucket,而是把原来的引用去掉,利用GC清除内存。

map 中数据的删除

如果理解了 map 的整体结构,那么查找、更新、删除的基本步骤应该都很清楚了。这里不再赘述。 值得注意的是,找到了 map 中的数据之后,针对key和value分别做如下操作:

1、如果``key``是一个指针类型的,则直接将其置为空,等待GC清除;
2、如果是值类型的,则清除相关内存。
3、同理,对``value``做相同的操作。
4、最后把key对应的高位值对应的数组index置为空。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创新者的解答

创新者的解答

【美】克莱顿•克里斯坦森、【加】迈克尔·雷纳 / 中信出版社 / 2013-10-10 / 49.00

《创新者的解答》讲述为了追求创新成长机会,美国电信巨子AT&T在短短10年间,总共耗费了500亿美元。企业为了保持成功记录,会面对成长的压力以达成持续获利的目标。但是如果追求成长的方向出现偏误,后果往往比没有成长更糟。因此,如何创新,并选对正确方向,是每个企业最大的难题。 因此,如何创新,并导向何种方向,便在于创新结果的可预测性─而此可预测性则来自于正确的理论依据。在《创新者的解答》中,两位......一起来看看 《创新者的解答》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具