Map底层原理
在Go语言中,map是内置的关联数组类型,它可以被视为一个键值对的集合。每个元素都是一个键值对,键必须是支持相等性比较的数据类型,值可以是任意类型。map是无序的,它不具有显式的索引,但可以通过键来快速查找和访问元素。
map的底层实现使用了哈希表,也就是哈希数组结构。当你创建一个map时,Go会分配一个哈希表来存储键值对。当你添加元素到map中时,会对键进行哈希计算,得到一个位置,然后将键值对存储在这个位置上。如果两个键通过哈希计算得到相同的位置,它们之间会形成一个链表
Map设计原理
1.hash值的使用
通过哈希函数,key可以得到一个唯一值,map将这个唯一值,分成高8位和低8位,分别有不同的用途
- 低8位:用于寻找当前key属于哪个bucket
- 高8位:用于寻找当前key在bucket中的位置,bucket有个tohash字段,便是存储的高8位的值,用来声明当前bucket中有哪些key,这样搜索查找时就不用遍历bucket中的每个key,只要先看看tohash数组值即可,提高搜索查找效率
map其使用的hash算法会根据硬件选择,比如如果cpu是否支持aes,那么采用aes哈希,并且将hash值映射到bucket时,会采用位运算来规避mod的开销
2.桶的细节设计
bmap结构,即桶,是map中最重要的底层实现之一,其设计要点如下:
- 桶是map中最小的挂载粒度:map中不是每一个key都申请一个结构通过链表串联,而是每8个kv键值对存放在一个桶中,然后桶再通以链表的形式串联起来,这样做的原因就是减少对象的数量,减轻gc的负担。
- 桶串联实现拉链法:当某个桶数量满了,会申请一个新桶,挂在这个桶后面形成链表,新桶优先使用预分配的桶。
- 哈希高8位优化桶查找key : 将key哈希值的高8位存储在桶的tohash数组中,这样查找时不用比较完整的key就能过滤掉不符合要求的key,tohash中的值相等,再去比较key值
- 桶中key/value分开存放 : 桶中所有的key存一起,所有的value存一起,目的是为了方便内存对齐
- 根据k/v大小存储不同值 : 当k或v大于128字节时,其存储的字段为指针,指向k或v的实际内容,小于等于128字节,其存储的字段为原值
- 桶的搬迁状态 : 可以根据tohash字段的值,是否小于minTopHash,来表示桶是否处于搬迁状态
3.map的扩容与搬迁策略
map底层扩容策略如下:
- map的扩容策略是新分配一个更大的数组,然后在插入和删除key的时候,将对应桶中的数据迁移到新分配的桶中去
map的搬迁策略如下:
- 由于map扩容需要将原有的kv键值对搬迁到新的内存地址,直接一下子全部搬完会非常的影响性能
- 采用渐进式的搬迁策略,将搬迁的O(N)开销均摊到O(1)的赋值和删除操作上
以下两种情况时,会进行扩容:
- 当装载因子超过6.5时,扩容一倍,属于增量扩容
- 当使用的溢出桶过多时间,重新分配一样大的内存空间,属于等量扩容,实际上没有扩容,主要是为了回收空闲的溢出桶
装载因子等于 map中元素的个数 / map的容量,即len(map) / 2^B
- 装载因子用来表示空闲位置的情况,装载因子越大,表明空闲位置越少,冲突也越多
- 随着装载因子的增大,哈希表线性探测的平均用时就会增加,这会影响哈希表的性能,当装载因子大于70%,哈希表的性能就会急剧下降,当装载因子达到100%,整个哈希表就会完全失效,这个时候,查找和插入任意元素的复杂度都是O(N),因为需要遍历所有元素.