1、简介
go的map底层是一个hash表(HashMap)
,表面上看map只有键值对结构,实际上在存储键值对的过程中涉及到了数组和链表。HashMap之所以高效,是因为其结合了顺序存储(数组)
和链式存储(链表)
两种存储结构。数组是HashMap的主干
,在数组下有一个类型为链表
的元素。
哈希函数会将传入的key值
进行哈希运算
,得到一个唯一的值。go语言把生成的哈希值一分为二
,比如一个key经过哈希函数,生成的哈希值为:8423452987653321,go语言会这它拆分为84234529,和87653321。那么,前半部分就叫做高位哈希值
,后半部分就叫做低位哈希值
。
高位哈希值:是用来确定当前的bucket(桶)有没有所存储的数据的。
低位哈希值:是用来确定,当前的数据存在了哪个bucket(桶)
2、hmap(a header of map)
hmap是map的最外层的一个数据结构,包括了map的各种基础信息、如大小、bucket。首先说一下,buckets这个参数,它存储的是指向buckets数组的一个指针
,当bucket(桶为0时)为nil。我们可以理解为,hmap指向了一个空bucket数组,并且当bucket数组需要扩容时,它会开辟一倍的内存空间,并且会渐进式
的把原数组拷贝,即用到旧数组
的时候就拷贝到新数组。
map的整体结构图
3、bmap(a bucket of map)
bucket(桶),每一个bucket最多放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的指针
4、hmap和bmap结构图
5、map的扩容
上面部分代表旧的有数据的bucket
,下面部分代表新生成的新的bucket。蓝色代表存有数据的bucket,橘黄色代表空的bucket。
扩容时map并不会立即把新数据做迁移
,而是当访问原来旧bucket的数据
的时候,才把旧数据做迁移
,如下图:
注意:这里并不会直接删除旧的bucket
,而是把原来的引用
去掉,利用GC清除内存
。
6、map中数据的删除
如果理解了map的整体结构,那么查找、更新、删除的基本步骤应该都很清楚了。这里不再赘述。
值得注意的是,找到了map
中的数据之后,针对key和value
分别做如下操作:
1、如果
key
是一个指针类型的,则直接将其置为空,等待GC清除;
2、如果是值类型的,则清除相关内存。
3、同理,对value
做相同的操作。
4、最后把key对应的高位值对应的数组index置为空。