Map底层原理

map是一种数据结构,用于存储一系列无序的键值对,里面是基于键来存储的,这样我们可以通过键很快的找到对应的值。

内部实现介绍

Go底层是一个散列表,散列表里头包含一组捅,当在存储、删除及查找键值对的时候,所有的操作都是需要选择一个捅,把操作映射时指定的键传给映射的散列函数进行计算,就能找到对应的捅。通过合理数量的桶来平衡键值对的分布,这样大大提高查找效率。

栗子:

上面声明一个map,键值都是string类型,首先,看一看键是字符串,在map底层是如何存储的。

将字符串作为map的键,底层会通过哈希函数计算出散列值,然而该散列值在映射的序号范围内表示可以用于存储的捅序号。得到的散列值用于选择那个捅,也用于存储在及查找指定的键值对是非常方便。

深入剖析map底层

Go的map有自己的底层实现原理,其中最核心是由hmapbmap这两个结构体实现。

(1)Map初始化,底层会做哪些骚动作?

假设,你初始化一个容量为5个元素的map

go/src/runtime/map.go

源码:

(2)添加数据时,又发生什么呢?

第一步:首先会通过你传的key,结合哈希因子生成哈希值

第二步:拿到哈希值后B位(哈希值是二进制)来确定该数据应该存储在那个捅(Bmap)

第三步:确定捅之后,就可以添加数据了,存储的是将高8位存储到Bmap里面tophash,添加捅满的时候,会通过overflow找到溢出捅。

源码:

(3)读取数据会发生什么?

第一步:结合哈希因子和键生成哈希值

第二步:通过哈希值的后B位的值来确定该键存储对应的捅(Bmap)

第三步:然后根据tophash(高8位)去捅里面查询对应数据

(4)Map扩容

何时出发扩容机制?

扩容条件:

  • map数据总个数 / 捅个数 > 6.5 ,会触发翻倍扩容
  • 使用太多的溢出捅(溢出捅使用太多会导致map处理速度降低)
    • B <= 15 已使用的溢出捅个数 >= 2的B次方时,触发等量扩容
    • B > 15 已使用的溢出捅个数 >= 2的15次方时,触发等量扩容

源码:

(5)扩容后数据迁移发生了什么?

翻倍扩容:如果发生翻倍扩容时,那么迁移是将旧捅数据导入到新捅,根据哈希值来迁移的,具体实现。

将旧捅遍历

(6)其他扩展

map作为函数参数,有什么特点?

注:该栗子,证明map作为函数参数,是地址传递,而不值传递。

(7)map特点

  • 键不能重复
  • 键必须可以哈希(也就是说必须能作为计算的哈希)
  • 无序

Map的知识点还有很多细节,值得我们学习,这里有些还没有讲到,比如Map并发安全吗?这块我放到并发的时候来讲。

创作不易,你的点赞支持,便是我创作最大动力!