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



