一、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 的两倍
oldbuckets unsafe.Pointer
//指示扩容进度,小于此地址的 buckets 迁移完成
nevacuate uintptr
extra *mapextra
}

二、扩容
1. 扩容条件
-
当溢出桶超过限制后,进行等量扩容,目的是整理溢出桶,提高检索效率
if overflow < 2^15 : overflow 桶数量大于2^B进行等量扩容 if overflow > 2^15 overflow 桶数量大于2^15就进行等量扩容
-
装载因子6.5, 当存储的元素超过后进行增量扩容,扩容一倍
元素总数 < 2^B * 6.5
2. 扩容时机
-
扩容过程是渐进性的,主要是防止一次扩容需要搬迁的 key 数量过多,引发性能问题。
-
触发扩容的时机是增加了新元素, 桶搬迁的时机则发生在赋值、删除期间,每次最多搬迁两个桶。
三、查找,删除,修改过程
- 先通过hash函数计算出key的哈希值—— ℎ𝑎𝑠ℎ。
- 在根据哈希值,计算出key所在桶的位值—— ℎ𝑎𝑠ℎ & (2𝐵−1)
- 遍历桶(bucket)中所有元素,对比tophash数组中i位置的值和本次要插入的key的tophash(hash的高8位)是否相等,如果tophash相等,还要进一步比较对应位置的key的值是否相等;相等则修改对应位置的value。否则找到空位,然后讲新的值插入。
- 如果桶中8个元素全部装满了,则需要将元素放到溢出桶中,没有溢出桶,则需要新建溢出桶,新建溢出桶的逻辑为优先使用初始化时创建的预分配溢出桶,如果预分配溢出桶耗尽,再新建。
四、遍历
- map 遍历的核心在于理解 2 倍扩容时,老 bucket 会分裂到 2 个新 bucket 中去。而遍历操作,会按照新 bucket 的序号顺序进行,碰到老 bucket 未搬迁的情况时,要在老 bucket 中找到将来要搬迁到新 bucket 来的 key
- 遍历过程中的时候触发扩容,不受影响,因为已经开始遍历的通不会受这个通是old还是正常情况的影响。