第
Golangmap实现原理深入分析
目录简介Map的底层内存模型Map的存与取底层代码寻址过程Map的扩容第一种情况第二种情况Map的有序性Map的并发
简介
本文主要通过探究在golang中map的数据结构及源码实现来学习和了解map的特性,共包含map的模型探究、存取、扩容等内容。欢迎大家共同讨论。
Map的底层内存模型
在goland的源码中表示map的底层struct是hmap,其是hashmap的缩写
typehmapstruct{
//map中存入元素的个数,golang中调用len(map)的时候直接返回该字段
countint
//状态标记位,通过与定义的枚举值进行操作可以判断当前是否处于这种状态
flagsuint8
Buint8//2^B表示bucket的数量,B表示取hash后多少位来做bucket的分组
noverflowuint16//overflowbucket的数量的近似数
hash0uint32//hashseed(hash种子)一般是一个素数
bucketsunsafe.Pointer//共有2^B个bucket,但是如果没有元素存入,这个字段可能为nil
oldbucketsunsafe.Pointer//在扩容期间,将旧的bucket数组放在这里,新buckets会是这个的两倍大
nevacuateuintptr//表示已经完成扩容迁移的bucket的指针,地址小于当前指针的bucket已经迁移完成
extra*mapextra//optionalfields
}
B是buckets数组的长度的对数,即bucket数组的长度是2^B。bucket的本质上是一个指针,指向了一片内存空间,其指向的struct如下所示:
//AbucketforaGomap.
typebmapstruct{
tophash[bucketCnt]uint8
}
但这只是表面(src/runtime/hashmap.go)的结构,编译期间会给它加料,动态地创建一个新的结构:
typebmapstruct{
topbits[8]uint8
keys[8]keytype
values[8]valuetype
paduintptr//内存对齐使用,可能不需要
overflowuintptr//当bucket的8个key存满了之后
}
bmap就是我们常说的桶的底层数据结构,一个桶中可以存放最多8个key/value,map使用hash函数得到hash值决定分配到哪个桶,然后又会根据hash值的高8位来寻找放在桶的哪个位置具体的map的组成结构如下图所示:
Map的存与取
在map中存与取本质上都是在进行一个工作,那就是:
查询当前k/v应该存储的位置。
赋值/取值,所以我们理解了map中key的定位我们就理解了存取。
底层代码
funcmapaccess2(t*maptype,h*hmap,keyunsafe.Pointer)(unsafe.Pointer,bool){
//map为空,或者元素数为0,直接返回未找到
ifh==nil||h.count==0{
returnunsafe.Pointer(zeroVal[0]),false
//不支持并发读写
ifh.flagshashWriting!=0{
throw(concurrentmapreadandmapwrite)
//根据hash函数算出hash值,注意key的类型不同可能使用的hash函数也不同
hash:=t.hasher(key,uintptr(h.hash0))
//如果B=5,那么结果用二进制表示就是11111,返回的是B位全1的值
m:=bucketMask(h.B)
//根据hash的后B位,定位在bucket数组中的位置
b:=(*bmap)(unsafe.Pointer(uintptr(h.buckets)+(hashm)*uintptr(t.bucketsize)))
//当h.oldbuckets