map的底层原理
路径:$GOROOT/src/runtime/map.go
通过阅读源码可知:
- 每个桶里最多装8个<key,value>对
那么当第9个落入当前bucket该怎么办呢?
再构建一个bucket,并通过overflow指针连接起来。这就是链表法。
- 用哈希查找表,使用链表法解决冲突
一层层组成了多级链表。
- 在编译器编译时,bmap的结构还会被处理成:
type bmap struct {
topbits [8]uint8
keys [8]keytype
values [8]valuetype
pad uintptr
overflow uintptr
}
- 创建map的底层调用了makemap函数。 任务:
设置哈希种子hash0:
计算B的大小:
- 思考:slice和map分别作为函数参数时的区别? 创建slice底层调用makeslice,它返回的是slice这个结构体;而makemap如上图所示返回的是hmap的指针。
key、map几连问
map用的是什么hash函数?
如果CPU支持aes,用aes,否则用memhash
key定位过程
函数:mapaccess
- key经过哈希计算后得到64bit的哈希值(以64位机器为例)
- 哈希值后B(bucket数组长度的对数)位表示在哪个bucket中。 例如B=5,哈希后五位为00110,则在下标为6的bucket中
- 哈希值前8位表示tophash值。 如10010111表示在bucket结构中寻找HOB hash值为151的key。
- 双层循环:外层遍历bucket和overflow bucket,内层遍历单个bucket所有槽位。
map是协程安全的吗?
不是。以map赋值过程为例:
函数会检查map的标志为flags。若flag为1,说明有其他协程正在执行写操作,而assign本身也是写操作,并发写会抛出panic。
插入或修改key
函数:mapassign 双层循环:外层遍历bucket和overflow bucket,内层遍历单个bucket所有槽位。
- 扩容是渐进的:如果当前正在扩容,必须保证当前bucket对应的老bucket已经全部迁移完成才能赋值
- 赋值操作是何时进行的?
实际上mapassign()返回的是value的地址,便于后续赋值
谈谈map的删除过程
前半部分和查找、插入类似
- 检测是否存在并发写操作(flag=1)
- 计算key的哈希,找到落入的bucket
- 设置flag
- 如果map在扩容,触发搬迁操作
- 找到key的位置:两层循环:外层遍历bucket和overflow bucket,内层遍历单个bucket所有槽位。
- 对key或value清零,count-1,对应tophash改为emptyOne
- 若当前位置后续的槽位都是emptyRest,将当前的emptyOne改成emptyRest;继续检查前一个位置,如果是emptyOne就改为emptyRest
关于最后两步的描述详见源码:
谈谈map的扩容过程
函数:hashGrow
-
由图:扩容触发的条件
- 装载因子超过阈值
- 有太多overflow的bucket
-
阈值是多少呢?6.5
(loadFactorDen是2)
-
bucket总数和overflow里面的bucket数的关系?
15是一个界限,B小于15时,bucket总数超过2^B次方;大于15时这个极限停留在2^15,不再增长
以上只是一些零碎知识点,核心流程在growWork()和evacuate()
growWork()调用evacuate()
evacuate()代码有点长,但可以总结如下:
- 如果是装载因子超过阈值,新的bucket数量是之前的一倍 要重新计算key的哈希
- 如果是overflow的bucket过多,新的bucket数量和原来相等 可按序号搬
谈谈map的遍历过程
- 大致思路:双层循环:外层遍历bucket和overflow bucket,内层遍历单个bucket所有cell,从cell中取出key、value。
- 注意事项:由于扩容机制的存在,如果遍历发生在扩容期间,会涉及遍历新老bucket的过程。而源码是这样处理的:
从it.startBucket(随机位置的it.offset号的cell开始遍历(随机位置),取出其中的key和value,直到又回到起点bucket。
这也解释了为什么Go语言中的map里的key是无序的(从1.0版本起)
如何比较两个map是否相等
满足三者之一即可:
- 都为nil:map1==map2
- 非空,长度相等,指向同一个实体对象
- 相应的key指向的value深度相等:遍历map中的元素,判断是不是每个都等
可以对key或value取值吗
不能,因为要考虑到扩容导致地址失效的问题
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情