作者|赵彦辉简介本文主要通过探索Map在golang中的数据结构和源码实现,包括map的模型探索、访问、扩展等,来学习和理解map的特性.欢迎大家一起讨论。Map的底层内存模型在goland的源码中说明了map的底层struct是hmap,是hashmap的缩写typehmapstruct{//map中存储的元素个数,len()时直接返回map)在golang中调用字段countint//状态标志位,可以通过&运算定义的枚举值flags来判断当前是否处于该状态uint8Buint8//2^B表示桶的个数,和B表示哈希桶分组后有多少位nooverflowuint16//溢出桶的大概个数hash0uint32//哈希种子(hashseed)一般为质数bucketsunsafe.Pointer//一共有2^B个桶,但是如果没有存储任何元素,这个字段可能是niloldbucketsunsafe.Pointer//扩容的时候,把旧的bucket数组放在这里,新的buckets会比这个大一倍。存储桶已迁移。extra*mapextra//可选字段}B是buckets数组长度的对数,即bucket数组的长度为2^B。bucket的本质是一个指针,指向一块内存空间,它指向的struct如下:surface(src/runtime/hashmap.go)结构,在编译时会加入,动态创建新结构:typebmapstruct{topbits[8]uint8keys[8]keytypevalues[8]valuetypepaduintptr//内存对齐,可能不需要溢出uintptr//当桶的8个key都满了}bmap就是我们常说的“桶”的底层数据结构。一个bucket最多可以存储8个key/value,map通过hash函数得到hash值决定分配到哪个bucket,然后根据hash的高8位寻找放在bucket的什么位置价值。有一个工作在进行,就是:查询当前k/v应该存放的位置。赋值/取值,这样我们就明白了key在map中的位置,也就明白了访问。底层代码funcmapaccess2(t*maptype,h*hmap,keyunsafe.Pointer)(unsafe.Pointer,bool){//map为空,或者元素个数为0,直接返回notfoundifh==nil||h.count==0{returnunsafe.Pointer(&zeroVal[0]),false}//不支持并发读写ifh.flags&hashWriting!=0{throw("concurrentmapreadandmapwrite")}//根据hash函数计算哈希值。注意不同类型的key可能使用不同的hash函数hash:=t.hasher(key,uintptr(h.hash0))//如果B=5,那么结果用二进制表示就是11111,返回的是所有的值1sintheBbitsm:=bucketMask(h.B)//根据hash的最后B位,定位桶数组中的位置b:=(*bmap)(unsafe.Pointer(uintptr(h.buckets)+(hash&m)*uintptr(t.bucketsize)))//当h.oldbuckets不为空时,表示map已经展开。//此时,新桶中可能没有旧内容。//所以它必须在旧的Ifc:=h.oldbuckets;c!=nil{if!h.sameSizeGrow(){//表示之前的桶只有一半,所以需要除以2m>>=1}oldb:=(*bmap)(unsafe.指针(uintptr(c)+(hash&m)*uintptr(t.bucketsize)))if!evacuated(oldb){b=oldb}}//tophash取其高8位值top:=tophash(hash)//一个桶存储了8个元素后,不能再放入,此时会创建一个新的桶并挂在原桶的溢出指针成员上//遍历当前桶的所有链式桶for;b!=零;b=b.overflow(t){//查询桶的8个位置i:=uintptr(0);我
