💡
map 底层:
  • go 运行时使用hash表实现map抽象
  • map 插入、查找、删除、遍历 在运行时会转换成 runtime/map.go 下的函数调用。 (一一映射)
  • map底层类型为 runtime.hmap , 是map类型的描述符,存放了map操作的所有信息。
    • count 元素个数 len函数返回值
    • flags - map所处状态标志,4个状态:iterator、oldIterator、hashWriting、sameSizeGrow
    • B:值为bucket数量以2为底的对数,默认常量为 【bucketCnt】1<<3, B = 8
    • noverflow: overflow bucket 大约数量
    • hash0:哈希函数的种子值
    • buckets:指向bucket的指针
    • oldbuckets:map扩容阶段老的bucket挂在位置
    • nevacaute:map扩容阶段的扩容计数器,所有下标号小于nevacaute的bucket都已完成了排空及迁移
    • extra:可选字段。某些情况下保证overflow bucket始终可用 不被GC
    •  
💡
真正用来存储数据的是 bucket
  • 默认bucket元素个数为8
  • map自动扩容时,会建立overflow bucket挂载到 bucket末尾的 overflow指针,形成链表结构,持续到下一次map扩容
 
💡
bucket 数组组成:(每个桶最多包含8对 kv)
  • tophash区域
    • 插入或查询map时,使用hash函数对key进行hash运算,一个寻址过程
      • v ,ok := m[”key”]
      • hashCode = hash (key)
    • hashCode 分为高位区 低位区
      • 高位区负责与bucket的tophash值进行比较,选定key
      • 低位区经mask后 选定bucket
    • tophash区域用于快速定位key,空间换时间
    •  
  • key存储区域
    • 连续的存储区域
    • runtime.maptype 记录key 类型、大小,运行时要知道map-key的大小。runtime运行map重写函数,第一个参数为*maptype
  • value存储区域
    • 连续的存储区域
go map 选择 kv分开存储 而不是连续存储,提高了算法复杂度,但减少了因内存对齐带来了内存浪费。可以节省近一半的空间。 (时间换空间)
 
超过128 bit 运行是map存的是 kv的指针

💡
map自动扩容时,会建立overflow bucket挂载到 bucket末尾的 overflow指针,形成链表结构,持续到下一次map扩容
  • 通过Loadfactor(负载因子)判断是否需要扩容
    • 当count > LoadeFactor * 2^B 或 overflow bucket过多时进行扩容
      • overflow bucket过多导致扩容,会建立规模一样的bucket进行迁移(在append、delete时)
      • LoadeFactor 导致扩容,会建立两倍规模的bucket 数组进行迁移
  • 原bucket数组挂在 oldbuckets上,直到迁移完成再释放。
 

💡
map并发
  • 可以并发读
  • 不支持并发写
  • 并发场景下使用sync.map
  • 因为动态扩容,map value不支持取地址
    • p := &m[key] ❌
 

💡
总结
  • 不要依赖map的元素遍历顺序
  • map线程不安全的 ,不支持并发写
  • map value不支持取地址 (动态扩容)
  • 尽量使用cap创建map
 
Go Map底层实现原理
Go Map底层实现原理
notion image
notion image
notion image
notion image