map根据key取value的值_Go 常见的数据结构 Map

3ca09cdf-b235-eb11-8da9-e4434bdf6706.svg

Golang 的 map 使用哈希表作为底层实现,一个哈希表里可以有多个哈希节点,即 bucket,而每个 bucket 就保存了 map 中的一个或一组键值对。

1、map 数据结构

type hmap struct {
 count     int // 当前保存的元素个数
 flags     uint8
 B         uint8  // 指示bucket数组大小
 noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
 hash0     uint32 // hash seed

 buckets    unsafe.Pointer // bucket数组指针,数组的大小为2^B
 oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
 nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

 extra *mapextra // optional fields
}

2、bucket 数据结构

type bmap struct {
 tophash [bucketCnt]uint8 // 存储哈希值的高8位
}

每个 bucket 可以存储 8 个键值对。tophash 是个长度为 8 的数组,哈希值相同的键(准确的说是哈希值低位相同的键)存入当前 bucket 时会将哈希值的高位存储在该数组中,以方便后续匹配。

3、哈希冲突

当有两个或以上数量的键被哈希到同一个 bucket 时,我们称这些键发生了冲突。

Go 使用链地址来解决冲突。由于每个 bucket 可以存放 8 个键值对,所以同一个 bucket 存放超过 8 个键值对时会在创建一个键值对,用类似链表的方式将 bucket 连接起来。

4、负载因子

负载因子用于衡量一个哈希表冲突情况,公式为:

负载因子 = 键数量 / bucket 数量

例如,对于一个 bucket 数量为 4,包含 4 个键值对的哈希表来说,这个哈希表的负载因子为 1。

哈希表需要将负载因子控制在合适大小范围,超过其阈值需要进行 rehash,即键值对重新组织:

  • 负载因子过小,说明空间利用率低。
  • 负载因子过大,说明冲突严重,存取率低。

每个哈希表的实现对负载因子容忍程度不同,比如 Redis 实现中负载因子大于 1 时就会触发 rehash,而 Go 则在负载因子达到 6.5 时才会触发 rehash,因为 Redis 的每个 bucket 只能存 1 个键值对,而 Go 的 bucket 可能存 8 个键值对,所以 Go 可以容忍更高的负载因子。

5、扩容

5.1、扩容的前提条件

为了保证访问效率,当新元素将要添加进 map 时,都会检查是否需要扩容,扩容实际上是以空间换时间的手段。触发扩容的条件有 2 个:

  • 负载因子 > 6.5 时,即平均每个 bucket 存储的键值对达到 6.5 个。
  • overflow 数量 > 2^15 时,即 overflow 数量超过 32768 时。

5.2、增量扩容

当负载因子过大时,就新建一个 bucket,新的 bucket 长度是原来的 2 倍,然后旧 bucket 数据搬迁到新的 bucket。

考虑到如果 map 存储了数以亿计的 key-value,一次性搬迁将会造成较大的延时,Go 采用逐步搬迁策略,即每次访问 map 时都会触发一次搬迁,每次搬迁 2 个键值对。

5.3、等量扩容

所谓等量扩容,实际上并不是扩大容量,bucket 数量不变,重新做一遍类似增量扩容的搬迁动作,把松散的键值对重新排列一次,以便 bucket 的使用率更高,进而保证更快的存取。在极端场景下,比如不断的增删,而键值对正好集中在一小部分的 bucket ,这样会造成 overflow 的 bucket 数量增多,但负载因子又不高,从而无法执行增量搬迁情况。

6、map 查找过程

  1. 根据 key 值算出哈希值。
  2. 取出哈希值低位与 hmap.B 取模确定 bucket 位置
  3. 取哈希值高位在 tophash 数组中查询
  4. 如果 tophash[i] 中存储值与哈希值相等,则去找到该 bucket 中的 key 值进行比较。
  5. 当前 bucket 没有找到,则继续从下一个 overflow 的 bucket 查找。
  6. 如果当前处于搬迁过程,则优先从 oldbuckets 查找

注:如果查找不到,也不会返回空值,而是返回相应类型的 0 值。

7、插入过程

  1. 根据 key 值算出哈希值。
  2. 取出哈希值低位与 hmap.B 取模确定 bucket 位置。
  3. 查找该 key 是否已经存在,如果存在则直接更新值。
  4. 如果没有找到 key,将 key 插入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值