1、简介

go的map底层是一个hash表(HashMap),表面上看map只有键值对结构,实际上在存储键值对的过程中涉及到了数组和链表。HashMap之所以高效,是因为其结合了顺序存储(数组)链式存储(链表)两种存储结构。数组是HashMap的主干,在数组下有一个类型为链表的元素。

哈希函数会将传入的key值进行哈希运算,得到一个唯一的值。go语言把生成的哈希值一分为二,比如一个key经过哈希函数,生成的哈希值为:8423452987653321,go语言会这它拆分为84234529,和87653321。那么,前半部分就叫做高位哈希值,后半部分就叫做低位哈希值

高位哈希值:是用来确定当前的bucket(桶)有没有所存储的数据的。
低位哈希值:是用来确定,当前的数据存在了哪个bucket(桶)

go语言 map 内存 go的map底层_go语言 map 内存

2、hmap(a header of map)

hmap是map的最外层的一个数据结构,包括了map的各种基础信息、如大小、bucket。首先说一下,buckets这个参数,它存储的是指向buckets数组的一个指针,当bucket(桶为0时)为nil。我们可以理解为,hmap指向了一个空bucket数组,并且当bucket数组需要扩容时,它会开辟一倍的内存空间,并且会渐进式的把原数组拷贝,即用到旧数组的时候就拷贝到新数组。

map的整体结构图

go语言 map 内存 go的map底层_键值对_02

3、bmap(a bucket of map)

bucket(桶),每一个bucket最多放8个key和value,最后由一个overflow字段指向下一个bmap,注意key、value、overflow字段都不显示定义,而是通过maptype计算偏移获取的。

go语言 map 内存 go的map底层_go语言 map 内存_03


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个键值对

go语言 map 内存 go的map底层_键值对_04


(3)第三部分,存储的是当bucket溢出时,指向的下一个bucket的指针

4、hmap和bmap结构图

go语言 map 内存 go的map底层_数据_05

5、map的扩容

go语言 map 内存 go的map底层_数组_06


go语言 map 内存 go的map底层_键值对_07


上面部分代表旧的有数据的bucket,下面部分代表新生成的新的bucket。蓝色代表存有数据的bucket,橘黄色代表空的bucket。

扩容时map并不会立即把新数据做迁移,而是当访问原来旧bucket的数据的时候,才把旧数据做迁移,如下图:

go语言 map 内存 go的map底层_数组_08


注意:这里并不会直接删除旧的bucket,而是把原来的引用去掉,利用GC清除内存

6、map中数据的删除

如果理解了map的整体结构,那么查找、更新、删除的基本步骤应该都很清楚了。这里不再赘述。
值得注意的是,找到了map中的数据之后,针对key和value分别做如下操作:

1、如果key是一个指针类型的,则直接将其置为空,等待GC清除;
2、如果是值类型的,则清除相关内存。
3、同理,对value做相同的操作。
4、最后把key对应的高位值对应的数组index置为空。