map
map 底层实现
- map 创建时(makemap)会返回指向 map 的指针, 所以不需要取地址就可以传递
- 底层由 buckets 数组组成, 数组中的元素称为 桶, 桶最多装 8 个 key
- hash 结果 低B位(2^B 个桶, B为map中桶的数量)决定在哪个桶, 高8位决定在桶的那个位置
- key, value 都不为指针且 size < 128 byte 时, 桶会被标记为不含指针, 避免 gc 扫描整个 hmap
- 桶中的8个key,value是按照 key 放一起, value 放一起的结构. 目的是避免不同数据类型的内存空隙, 节省内存
- 每个桶内有溢出部分,称为溢出桶,可以为多个,与主桶成链表形式,遍历时会遍历链表
线程是否安全
- 非安全, 查找/赋值/遍历/删除过程都会检测写标志,一旦发现写标志=1,会直接 panic
- 赋值和删除操作会将写标志设置为1
扩容过程
- loadFactor := count / (2^B); 插入时检查
- loadFactor > 6.5
- overflow 中的 bucket 数量过多; B < 15 以 2^B 判断, B >= 15 以 2^15 判断
- 渐进式扩容, 在每次 插入/修改/删除 key 的时候,会尝试进行搬迁操作
遍历过程
- 非扩容中遍历: 遍历所有 bucket 以及 overflow bucket, 然后挨个遍历 bucket 中的 cell, 从所有的 cell 中取出 key, value 即可
- 扩容中遍历: 新旧桶都要遍历
赋值过程
- 计算 hash, 后 2^B 位定位到桶, 前8位定位到桶中的位置
- 桶满了会创建 overflow bucket 存放
删除过程
- hash 定位位置进行移除; 先定位 bucket, 然后定位 cell
- 如果处于扩容中, 会触发一次搬迁操作
对 map 里的元素取地址
不行, 因为一旦扩容, key value 的位置就会改变, 保存的地址也就失效了
边遍历边删除
单个协程中理论是可行, 但是遍历结果可能会包含删除了的 key,也可能不包含.
比较 map 是否相等
需要遍历比较, 或者使用 reflect.DeepEquel()
进行比较
集合运算
- key 在扩容时必定无序
- 不扩容的情况下, Go 增加了随机数的 bucket index 开始遍历, 固定这一个无序的特性
哪些类型可以作为 key
可以比较的类型都可以作为 key