1.map内部结构体
map的底层数据结构是hmap结构体。
buckets:指向桶数组的指针
count:key的数量
B:桶的数量的对数值 (count与B计算增量扩容)
noverflow:溢出桶的数量(用于等量扩容)
hash0:hash随机值 (用于计算key的hash值增加随机性,减少碰撞)
oldbuckets:扩容过程中指向旧的桶的指针,还可以判断bucket桶是否在扩容中(oldbuckets!=nil)
nevacuate:扩容的进度值(小于此值的已经完成扩容)
flags:迭代map或者对map进行写操作的时候,会记录该标志位,用于一些并发场景的检测校验
桶的数据结构是bmap
每个桶里包括8个key和8个value,和对应的8个tophash值,其中tophash值是hash值的高8位
tophash:用于map查找中第一次比对,如果tophash值相同,接下来比对key值。
桶中数据的存储:8个key放在一起,8个value放在一起,作用是减少填充的空隙,节省空间。
overflow是一个指针,指向下一个桶,桶与桶形成链表存储key-value值
存储结构图如下

2.map的初始化
map的初始化底层有3种函数makemap_small,makemap64,makemap
makemap_small:当map编译期确定初始长度不大于8,只创建hmap,不初始化buckets。
makemap64:当make函数传递的长度参数类型是int64时候,调用该函数,底层仍然是复用makemap。
makemap:初始化hash0加入随机性,计算对数B,并初始化buckets。
makemap_small源码
makemap源码如下
、makemap64源码
3.map查询
map查询底层源码是mapaccess1,mapaccess2
mapaccess1没有key是否存在的bool值,mapaccess2有key是否存在的bool值
查找的过程
1.计算key的hash
2.hash与低B位取&获得桶的位置
3.获得hash的高8位作为tophash值,比对tophash值与桶中的tophash值
4.如果tophash值相同,比对key值,key值,获得value,否则继续向下寻找,如果一直找不到,返回类型的0值
map查询源码
mapaccess2 源码
4.map新增
map新增元素底层调用的是mapassign
新增过程
1.计算key的hash
2.hash与低B位取&获得桶的位置
3.获得hash的高8位作为tophash值,比对tophash值,如果tophash相同,比对key值,如果key相同,则更新value
4.如果tophash值不同,且不为空,则继续向下寻找,直到第一个位置为空的位置,并插入元素。
mapassign源码
5.map扩容
map的扩容包括两种情况
默认的负载因子是6.5
1.count/B>6.5 做增量扩容,容量是原来的二倍,扩容过程是渐进扩容,整个扩容拆散在每一次的写过程中(新增和删除),用户无感知,每次最多扩容2个bucket
2.count/B<6.5 && noverflow>32767 做等量扩容,容量不变,会开辟新的bucket数组存放新元素。采用渐进式扩容。每次最多扩容2个bucket
map 新增源码
增量扩容和等量扩容,此处仅分配内存,真实的扩容发生在后续的写操作中
真正执行扩容的逻辑
扩容逻辑是先copy到目标地址,再删除旧的引用
6.map删除元素
map 删除底层源码调用的是 mapdelete
1.查找key,计算key的hash值,利用低B位找到对应的桶,遍历桶中元素的tophash,如果tophash值相同,比对key值,如果key值也相同,重置key,value值为nil,tophash为1(标记元素为空),否则一直循环遍历。
源码如下:
7.map遍历是有序的吗?
map 遍历的底层源码依赖mapiterinit和mapiternext,map的遍历是无序的,map遍历会随机选择一个bucket和一个offset进行遍历
map遍历源码
mapiterinit
mapiternext
8.是否能在迭代过程中删除元素
可以在遍历的过程中删除元素,删除的过程中把对应key,value置为nil,修改tophash的值为1,不会导致下标下移,故在遍历的过程中可以删除元素。
案例1
程序运行结果:
