map的基本组成
(go版本为1.10.12,实现map的源代码在文件/src/runtime/hashmap.go中)
- go语言的map用hash实现,相应的一个map由hmap结构体管理;
2. go语言抽象了bucket,桶的概念,他相当于一个存放连续内存的容器;每个桶的头部是bmap,之后是8个key,再是8个value,最后是1个溢出指针;当一个bucket溢出时可以挂载额外的桶,overflow指向它;
3. 一个map含有多个桶;根据map中key的hash值,散射到不同的bucket中;
由结构可知,访问速度是O(1)。
可以参考下这图(链接在图片注释中):
一些常量
扩散因子:loadFactorNum / loadFactorDen = 6.5;
元素数量 >= (hash桶数量(2^hashmap.B) * 6.5 / 8) 时,触发扩容
ps:2^hashmap.B 为桶的数量,下面会说这个 B;
tophash用来存储key的hash值的高八位,这些常数又用来表示tophash状态,下面会讨论;
关于map定义的源码
- 关于map的定义在hashmap文件中,如下图所示:
首先是hmap,作为一个map的头部,源码中可以看出它包含了所有应该有的。下图做了部分说明:
2. bmap的定义十分简单又特别重要,它存储了一个名为 tophash 的uint8类型数组,数组长度为 bucketCnt,这是个常量,被赋值为8;tophash用来存储key哈希值的高八位;但是又要分情况,如果它的值小于minTopHash,就用来表示状态。
特别注意:实际分配内存时会申请一个更大的内存空间A,A的前8字节为bmap,后面依次跟8个key、8个value、1个溢出指针,map的桶结构实际指的是内存空间A;
参考:(这篇文章go版本和我的不一样,我的go版本更新)
Golang之map tophash详解blog.csdn.net当tophash对应的K/V被使用时,存的是key的哈希值的高8位;当tophash对应的K/V未被使用时,存的是K/V对应位置的状态;下面是map源码中对tophash状态值的定义。
当tophash[i] < 4时,表示存的是状态;
当tophash[i] >= 4时,表示存的是哈希值;
那么问题来了,如果key的哈希值高8位小于minTopHash时,这时候怎么区分是存的状态还是哈希值?
源码中的解决方法是将计算得到的hash值做一个判断,当计算的哈希值小于minTopHash时,会直接在原有哈希值基础上加上minTopHash,确保哈希值一定大于minTopHash。
上述其他四种状态也有相应的作用:
举上面链接中的例子:
扩容迁移,要把旧桶1的元素迁到新桶,因为新桶长度增长了一倍,因此旧桶1元素可能被迁移到新桶的1或5。当元素迁移到了1时,把旧桶tophash置为evacuatedX;反之,迁移到了5时,tophash置为evacuatedY。要注意置的是旧桶的tophash。
3. mapextra,额外桶,包含三个元素
hmap.extra.nextOverflow初始时指向内存A中的后段,即hash数组结尾的下一个桶,也即第1个预留的溢出桶。所以当hash冲突需要使用到新的溢出桶时,会优先使用上述预留的溢出桶,hmap.extra.nextOverflow依次往后偏移直到用完所有的溢出桶,才有可能会申请新的溢出桶空间。