Map的底层实现
go的map底层是一个hash表,表面上看只有键值对结构,实际上存储键值对的过程中涉及到了数组和链表。map之所以高效,是因为结合了数组和链表,数组存储的元素为链表。
哈希
哈希函数会将传入的key值进行哈希运算,得到一个唯一的值,go语言把生成的hash值一分为2,即高位hash值和低位hash值。
比如一个key经过哈希函数,生成的哈希值为:8423452987653321,go语言会这它拆分为84234529,和87653321。那么,前半部分就叫做高位哈希值,后半部分就叫做低位哈希值。
高位hash:用来确定当前的桶有没有所存储的数据的(即确定数组的索引)
低位hash:用来确定,当前的数据存在了哪个桶
hmap
hmap是map的最外层的一个数据结构,包括了map的各种基础信息、如大小、bucket。首先说一下,buckets这个参数,它存储的是指向buckets数组的一个指针,当bucket(桶为0时)为nil。我们可以理解为,hmap指向了一个空bucket数组,并且当bucket数组需要扩容时,它会开辟一倍的内存空间,并且会渐进式的把原数组拷贝,即用到旧数组的时候就拷贝到新数组。
bmap
桶,每一个桶最多放8个key和value,最后由一个overflow字段指向下一个bmap,注意key,value,overflow字段都不显示定义,而是通过maptype计算偏移获取的
bucket这三部分内容决定了它是怎么工作的:
(1)它的tophash 存储的是哈希函数算出的哈希值的高八位(8个)。是用来加快索引的。因为把高八位存储起来,这样不用完整比较key就能过滤掉不符合的key,加快查询速度当一个哈希值的高8位和存储的高8位相符合,再去比较完整的key值,进而取出value。当超过8个元素需要存入某个bucket时,hmap会拓展该bucket。
(2)第二部分,存储的是key 和value,就是我们传入的key和value,注意,它的底层排列方式是,key全部放在一起,value全部放在一起。当key大于128字节时,bucket的key字段存储的会是指针,指向key的实际内容;value也是一样。这样排列好处是在key和value的长度不同的时候,可以消除padding带来的空间浪费。并且每个bucket最多存放8个键值对。
(3)第三部分,存储的是当bucket溢出时,指向的下一个bucket的指针
hmap和bmap的结构图
Map扩容
上面部分代表旧的有数据的bucket,下面部分代表新生成的新的bucket。蓝色代表存有数据的bucket,橘黄色代表空的bucket。
扩容时map并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移,如下图:
map中的删除
找到了map中的数据之后,针对key和value分别做如下操作:
1、如果key是一个指针类型的,则直接将其置为空,等待GC清除;
2、如果是值类型的,则清除相关内存。
3、同理,对value做相同的操作。
4、最后把key对应的高位值对应的数组index置为空。