Loading

Golang map 的底层原理

前言

本文介绍 golang 中 map 的实现方式, 希望对读者和我有所帮助

结构

map是 go 语言中的基础的数据结构, 在寻找指定key时, 复杂度是O(1), 在某些场景能发挥很大的作用

golang 的 map 是 hashmap, 实现方式是数组+链表, 并且使用拉链法来取消 hash 的冲突

map 主要有两个核心的数据结构, hmapbmap(bucket), 为什么说他核心呢? 因为 map 的实现就是依靠这两个结构体

hmap的结构有以下几个字段

  • 元素个数: int
  • flags: uint8
  • 扩容字段: uint8
  • 溢出的 bucket 数量: uint16
  • 用于扩容的指针: *mapextra
  • buckets 数组指针: unsafa.Pointer
  • 搬迁进度: uintptr
  • 扩容时用于复制的 buckets 数组: unsafe.Pointer
  • hash seed: uint32

这里你需要重点了解的是buckets 数组指针, 也就是bmap, 那么bmap的结构如下

  • 高位哈希: [8]uint8
  • 字节数组(存储 key 和 value 的数组): []bytes
  • 指向扩容 bucket 的指针: pointer

bmap字节数组中, 存储了我们真正的keyvalue

高位哈希中存储了key的索引

指向扩容 bucket 的指针则存储了下一个bmap的位置, 所以说 bucket 是链表形式

对于bmap中的字节数组部分存储这个bmap里面的所有keyvalue, 他是一个数组, 里面存储的结构是这样的

key0, key1, key2, value0, value1, value2

是将所有的 keykey排在一起,valuevalue排在一起

这是为了内存对齐, 目的是减少空间浪费

也就是说, hmap的结构应该是

		       hmap
														
bmap0  bmap1  bmap2
|      |      |
bmap3  bmap4  bmap5
|      |      |

这种关系(我真是灵魂画手😘)

大致理解一下, 就是一个hmap里面包含了一个切片, 切片的每一个元素是bmap, 而每一个bmap里含有这个bmapkeyvalue(多个), 还有下一个bmap的指针地址

新增 key 时

当新增一个keyvalue时, 会先对这个key进行哈希函数计算, 得到一个唯一的值, 是一个数字, 将这个数字切分为两个部分高位低位, 使用低位寻找这个key应该存储到哪个bmap中, 高位则记录了这个keybmap的位置

map 扩容

当 map 空间不够时, 会将bmap的数组数量扩充一倍, 产生一个新的bmap数组

然后把老的数据迁移到新的数组中

这个转移并不是立刻完成的, 而是当访问到具体的某个bmap的时候, 才会把这个bmap中的数据转移到新的数组

而且, 将老的bmap转移到新的列表之后, 并不会将老的bmap删除, 而是留给 GC 去清理

查找 key 时

获取需要查找的key, 转成数字并切分为高位低位, 通过低位定位bmap位置. 通过高位bmap中寻找具体的值

posted @ 2022-09-30 10:50  ChnMig  阅读(451)  评论(0)    收藏  举报