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

程序运行结果: