美文网首页
golang Map 实现原理——Map

golang Map 实现原理——Map

作者: 夜空中乄最亮的星 | 来源:发表于2021-05-21 15:34 被阅读0次

map 的设计也被称为 “The dictionary problem”,它的任务是设计一种数据结构用来维护一个集合的数据,并且可以同时对集合进行增删查改的操作。最主要的数据结构有两种:

  1. 哈希查找表(Hash table):时间复杂度:O(1) ,乱序
  2. 搜索树(Search tree): 一般采用自平衡搜索树,包括:AVL 树,红黑树,时间复杂度:O(logN),一般有序

map 内存模型

// A header for a Go map.
type hmap struct {
    // 元素个数,调用 len(map) 时,直接返回此值
    count     int
    flags     uint8
    // buckets 的对数 log_2
    B         uint8
    // overflow 的 bucket 近似数
    noverflow uint16
    // 计算 key 的哈希的时候会传入哈希函数
    hash0     uint32
    // 指向 buckets 数组,大小为 2^B
    // 如果元素个数为0,就为 nil
    buckets    unsafe.Pointer
    // 等量扩容的时候,buckets 长度和 oldbuckets 相等
    // 双倍扩容的时候,buckets 长度会是 oldbuckets 的两倍
    oldbuckets unsafe.Pointer
    // 指示扩容进度,小于此地址的 buckets 迁移完成
    nevacuate  uintptr
    extra *mapextra // optional fields
}

其中buckets是一个指针,最终它指向的是一个结构体

type bmap struct {
    tophash [bucketCnt]uint8
}

但这只是表面(src/runtime/hashmap.go)的结构,编译期间会给它加料,动态地创建一个新的结构:

type bmap struct {
    topbits  [8]uint8
    keys     [8]keytype
    values   [8]valuetype
    pad      uintptr
    overflow uintptr
}

bmap 就是我们常说的“桶”,桶里面会最多装 8 个 key,这些 key 之所以会落入同一个桶,是因为它们经过哈希计算后,哈希结果是“一类”的。在桶内,又会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置(一个桶内最多有8个位置)。如果有第 9 个 key-value 落入当前的 bucket,那就需要再构建一个 bucket ,通过 overflow 指针连接起来。

overflow是一个指针,指向一个元素个数为2的数组,数组的类型是一个指针,指向一个slice,slice的元素是桶(bmap)的地址,这些桶都是溢出桶;

type mapextra struct {
    // overflow[0] contains overflow buckets for hmap.buckets.
    // overflow[1] contains overflow buckets for hmap.oldbuckets.
    overflow [2]*[]*bmap
    // nextOverflow 包含空闲的 overflow bucket,这是预分配的 bucket
    nextOverflow *bmap
}

为什么有两个?因为Go map在hash冲突过多时,会发生扩容操作,为了不全量搬迁数据,使用了增量搬迁

[0]表示当前使用的溢出桶集合
[1]是在发生扩容时,保存了旧的溢出桶集合;

当 map 的 key 和 value 都不是指针,并且 size 都小于 128 字节的情况下,会把 bmap 标记为不含指针,这样可以避免 gc 时扫描整个 hmap。但是,我们看 bmap 其实有一个 overflow 的字段,是指针类型的,破坏了 bmap 不含指针的设想,这时会把 overflow 移动到 extra 字段来。
extra存在的意义在于防止溢出桶被gc

Map 内存模型示意图

key定位

  • hash函数:在程序启动时,会检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash
  • Key定位过程:
    key 经过哈希计算后得到哈希值,共 64 个 bit 位(64位机,32位机就不讨论了,现在主流都是64位机),计算它到底要落在哪个桶时,只会用到最后 B 个 bit 位。如果 B = 5,那么桶的数量,也就是 buckets 数组的长度是 2^5 = 32。
    例如,现在有一个 key 经过哈希函数计算后,得到的哈希结果是:
110010111 | 000011110110110010001111001010100010010110010101010 │ 01010

用最后的 5 个 bit 位,也就是 01010,值为 10,也就是 10 号桶。这个操作实际上就是取余操作,但是取余开销太大,所以代码实现上用的位操作代替。

再用哈希值的高 8 位,找到此 key 在 bucket 中的位置,这是在寻找已有的 key。最开始桶内还没有 key,新加入的 key 会找到第一个空位,放入。

buckets 编号就是桶编号,当两个不同的 key 落在同一个桶中,也就是发生了哈希冲突。冲突的解决手段是用链表法:在 bucket 中,从前往后找到第一个空位。这样,在查找某个 key 时,先找到对应的桶,再去遍历 bucket 中的 key。


key定位过程示意图

删除

删除某个key的操作与分配类似,由于hashmap的存储结构是数组+链表,所以真正删除key仅仅是将对应的slot设置为empty,并没有减少内存

相关文章

  • Golang之Map源码

    引用 深入 Go 的 Map 使用和实现原理 哈希表 深度解密Go语言之map Golang map 的底层实现 使用

  • golang Map 实现原理——Map

    map 的设计也被称为 “The dictionary problem”,它的任务是设计一种数据结构用来维护一个集...

  • Golang Map实现原理

    目录 一、map的操作 声明 map的零值为 nil 。nil 映射既没有键,也不能添加键。var m map[s...

  • hash map 实现原理

    HashMap 的存取实现 1.1 put refrencehash map实现hash map实现原理

  • 【golang】HashMap原理和实现

    原理 我们都知道怎么使用goLang中的map来存储键值对类型的数据,但是它的内部实现是怎么样的? 其实map是一...

  • HashMap原理和实现

    原理 我们都知道怎么使用goLang中的map来存储键值对类型的数据,但是它的内部实现是怎么样的? 其实map是一...

  • 【golang】HashMap原理和实现

    原理 我们都知道怎么使用goLang中的map来存储键值对类型的数据,但是它的内部实现是怎么样的? 其实map是一...

  • 深入之数组方法的实现原理

    map的用法和实现原理: map即映射,将原数组映射返回新数组。 用法: 实现原理: filter的用法和实现原理...

  • Go map底层实现

    golang map源码详解Golang map 如何进行删除操作?

  • 彻底理解Golang Map

    本文目录如下,阅读本文后,将一网打尽下面Golang Map相关面试题 面试题 map的底层实现原理 为什么遍历m...

网友评论

      本文标题:golang Map 实现原理——Map

      本文链接:https://www.haomeiwen.com/subject/ewdwjltx.html