介绍

Golang中实现图底层的原理是什么?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

<强> 1。数据结构及内存管理

hashmap的定义位于src/运行/hashmap。中,首先我们看下hashmap和桶的定义:

 hmap struct类型{
  计数int//元素的个数
  旗帜uint8//状态标志
  B uint8//可以最多容纳6.5 * 2 ^ B个元素,6.5为装载因子
  noverflow uint16//溢出的个数
  hash0 uint32//哈希种子
  
  桶不安全。指针//桶的地址
  oldbuckets不安全。//指针旧桶的地址,用于扩容
  nevacuate uintptr//搬迁进度,小于nevacuate的已经搬迁
  溢出* [2]* []* bmap
  }

其中,溢出是一个指针,指向一个元素个数为2的数组,数组的类型是一个指针,指向一个切片,切片的元素是桶(bmap)的地址,这些桶都是溢出桶;为什么有两个?因为去地图在哈希冲突过多时,会发生扩容操作,为了不全量搬迁数据,使用了增量搬迁,[0]表示当前使用的溢出桶集合,[1]是在发生扩容时,保存了旧的溢出桶集合;溢出存在的意义在于防止溢出桶被gc。

//一桶去地图。
  bmap struct类型{//每个元素散列值的高8位,如果tophash [0] & lt;minTopHash,表示这个桶的搬迁状态
  tophash bucketCnt uint8//接下来是8个关键,8个值,但是我们不能直接看到;为了优化对齐,去采用了钥匙放在一起,价值放在一起的存储方式,//再接下来是哈希冲突发生时,下一个溢出桶的地址
  }

tophash的存在是为了快速试错,毕竟只有8位,比较起来会快一点。

从定义可以看的出,不同于STL中地图以红黑树实现的方式,Golang采用了散列表的实现,解决冲突采用的是链地址法。也就是说,使用数组+链表来实现地图。特别的,对于一个键,几个比较重要的计算公式为:

keyhashhashtopbucket indexkeyhash:=alg。散列(关键uintptr (h.hash0)):=uint8(散列在祝辞(系统。PtrSize * 8 - 8))桶:=散列,(uintptr (1) & lt; & lt; h。B - 1),即哈希% 2 B ^

例如,对于B=3,当哈希(关键)=4时,hashtop=0,桶=4,当哈希(关键)=20时,hashtop=0,桶=4;这个例子我们在搬迁过程还会用的到。

内存布局类似于这样:

 Golang中实现图底层的原理是什么

hashmap-buckets

<强> 2。创建——makemap

地图的创建比较简单,在参数校验之后,需要找到合适的B来申请桶的内存空间,接着便是穿件hmap这个结构,以及对它的初始化。

 Golang中实现图底层的原理是什么

makemap

<强> 3。访问——mapaccess

对于给定的一个关键,可以通过下面的操作找到它是否存在

 Golang中实现图底层的原理是什么

形象。png

方法定义为

//返回键,如果没有找到,返回零
  func mapaccess1 (t * maptype h * hmap,关键unsafe.Pointer) unsafe.Pointer//返回键和存在。如果没有找到,返回nil,假的
  func mapaccess2 (t * maptype h * hmap,关键unsafe.Pointer)(不安全。指针,bool)//返回键和值。如果没有找到,返回零,零
  func mapaccessK (t * maptype h * hmap,关键unsafe.Pointer)(不安全。指针,unsafe.Pointer) 

可见在找不到对应的关键的情况下,会返回零

<强> 4。分配,mapassign

为一个关键分配空间的逻辑,大致与查找类似,但增加了写保护和扩容的操作,注意,分配过程和删除过程都没有在oldbuckets中查找,这是因为首先要进行扩容判断和操作,如下:

 Golang中实现图底层的原理是什么

 Golang中实现图底层的原理是什么

指定

<代码>扩容是整个hashmap的核心算法,我们放在第6部分重点研究。

新建一个溢出桶,并将其拼接在当前桶的尾部,实现了类似链表的操作:

//获取当前桶的溢出桶
  func (b * bmap)溢出(t * maptype) * bmap {
  返回* (* * bmap)(添加(unsafe.Pointer (b), uintptr (t.bucketsize) -sys.PtrSize))
  }//设置当前桶的溢出桶
  func (h * hmap) setoverflow (t * maptype, b, ovf * bmap) {
  h.incrnoverflow ()
  如果t.bucket.kind& kindNoPointers !=0 {
  h.createOverflow ()//重点,这里讲溢出桶附加到溢出[0]的后面
  * h。溢出[0]=append (* h。溢出[0],ovf)
  }
  * (* * bmap)(添加(unsafe.Pointer (b), uintptr (t.bucketsize) -sys.PtrSize))=ovf
  }