当前位置: 首页 > 后端技术 > PHP

深入理解Gomap:初始化和访问元素

时间:2023-03-29 18:08:11 PHP

从这篇文章开始,我们一起来探索Gomap的奥秘,看看它内部是如何组成的,有哪些值得注意的地方?第一篇文章会讨论初始化和访问元素相关的章节,我们会带着问题来学习,比如:初始化的时候会不会立即分配内存?底层数据是如何存储的?底层如何使用key查找数据?底层如何解决哈希冲突?这么多数据类型,底层怎么处理?...原文地址:深入理解Gomap:元素数据结构的初始化和访问首先我们看一下Gomap的基本数据结构,先有个大概的印象hmaptypehmapstruct{countintflagsuint8Buint8noverflowuint16hash0uint32bucketsunsafe.Pointeroldbucketsunsafe.Pointernevacuateuintptrextra*mapextra}typemapextrastruct{overflow*[]*bmapoldoverflow*[]*bmapnextOverflow*bmap}count:map的大小,即len()的值。指映射中键值对的数量。flags:状态标志,主要与goroutine编写扩展机制的状态控制有关。并发读写的判断条件之一是取值B:bucket,最大可容纳的元素个数,取值为loadfactor(默认6.5)*2^B,是一个索引为2的nooverflow:溢出桶数hash0:哈希因子buckets:保存当前桶数据的指针地址(指向一个连续的内存地址,主要存放键值对数据)oldbuckets,保存旧桶的指针地址nevacuate:迁移进度extra:原buckets满载后,会发生扩容Go机制中使用了增量扩容,具体如下:hmap.oldbuckets(old)溢出桶nextOverflow是空闲溢出桶的指针地址在这里要注意几点,如下:如果键值不包含指针d允许内联。桶会被标记为不包含指针,使用额外的存储溢出桶可以避免GC扫描整个地图,节省不必要的开销。前面提到,Go使用增量扩展。Buckets和oldbuckets也是扩容相关的载体。一般只用buckets,oldbuckets是空的。但是如果在扩容,旧的buckets不会是空的,buckets的大小也会发生变化。当hint大于8时,*mapextra将作为溢出桶。如果小于8,则存入bucketsbucketbucketCntBits=3bucketCnt=1<int(maxSliceCap(t.bucket.size)){hint=0}如果h==nil{h=new(hmap)}h.hash0=fastrand()B:=uint8(0)foroverLoadFactor(hint,B){B++}h.B=Bifh.B!=0{varnextOverflow*bmaph.buckets,nextOverflow=makeBucketArray(t,h.B,nil)ifnextOverflow!=nil{h.extra=new(mapextra)h.extra.nextOverflow=nextOverflow}}returnh}根据传入的bucket类型,得到该类型可以申请的最大容量。并且其长度make(map[k]v,hint)进行边界值检查初始化hmap初始化hashfactor根据传入的hint,计算出一个能容纳hint元素的bucketB的最小值分配,并初始化hash表。如果B为0,buckets会延迟分配,如果大于0,会立即分配,并返回初始化的hmap。这里可以注意到,(当hint大于等于8时)第一次初始化map时,会调用makeBucketArray来分配buckets。因此,我们常说在初始化时指定一个合适大小的容量。可以提高性能。如果容量太小,并且有很多新的键值对。会导致频繁分配bucket、扩容、迁移等rehash动作。最后的结果是性能直接下降(敲黑板),当hint小于8时,这个问题相对不那么突出,如下:funcmakemap_small()*hmap{h:=new(hmap)h.hash0=fastrand()returnh}图形访问用法v:=m[i]v,ok:=m[i]函数原型有几种实现map元素访问的方法,主要有针对32/64位,string的特殊处理类型,一般函数原型如下:(t*maptype,h*hmap,keyunsafe.Pointer)(unsafe.Pointer,unsafe.Pointer)mapaccess1_fat(t*maptype,h*hmap,key,zerounsafe.Pointer)unsafe.Pointermapaccess2_fat(t*maptype,h*hmap,键,零unsafe.Pointer)(unsafe.Pointer,bool)mapaccess1_fast32(t*maptype,h*hmap,keyuint32)unsafe.Pointermapaccess2_fast32(t*maptype,h*hmap,keyuint32)(unsafe.Pointer,bool)maassign_fast32(t*maptype,h*hmap,keyuint32)unsafe.Pointermaassign_fast32ptr(t*maptype,h*hmap,keyunsafe.Pointer)unsafe.Pointermapaccess1_fast64(t*maptype,h*hmap,keyuint64)unsafe.Pointer...mapacaccess1_faststr(t*maptype,h*hmap,kystring)unsafe.Pointer...mapaccess1:返回h[key]的指针地址,如果key不在map中,则返回对应类型的零值mapaccess2:returnh[key]如果key不在map中,则返回一个零值和一个布尔值,用于判断源码funcmapaccess1(t*maptype,h*hmap,keyunsafe.Pointer)unsafe.Pointer{...如果h==nil||h.count==0{returnunsafe.Pointer(&zeroVal[0])}ifh.flags&hashWriting!=0{throw("concurrentmapreadandmapwrite")}alg:=t.key.alghash:=alg.hash(key,uintptr(h.hash0))m:=bucketMask(h.B)b:=(*bmap)(add(h.buckets,(hash&m)*uintptr(t.bucketsize)))ifc:=h.oldbuckets;c!=nil{if!h.sameSizeGrow(){//过去有一半的桶;掩码再减少二的幂。m>>=1}oldb:=(*bmap)(add(c,(hash&m)*uintptr(t.bucketsize)))if!evacuated(oldb){b=oldb}}top:=tophash(hash)对于;b!=零;b=b.溢出(t){对于我:=uintptr(0);我