教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang map之tophash详解

Golang map之tophash详解

发布时间:2021-05-27   编辑:jiaochengji.com
教程集为您提供Golang map之tophash详解等资源,欢迎您收藏本站,我们将为您提供最新的Golang map之tophash详解资源

Golang之map tophash详解

tophash是一个长度为8的数组,它不仅仅用来存放key的哈希高8位,在不同场景下它还可以标记迁移状态,bucket是否为空等。弄懂tophash对我们深入了解map实现非常重要。

如上图:每一个tophash唯一对应一个K/V对。

tophash值含义

当tophash对应的K/V被使用时,存的是key的哈希值的高8位;当tophash对应的K/V未被使用时,存的是K/V对应位置的状态。

下面是map源码中对tophash状态值的定义。

@(src/runtime/map.go)

emptyRest      = 0 
emptyOne       = 1 
evacuatedX     = 2 
evacuatedY     = 3
evacuatedEmpty = 4
minTopHash     = 5 

当tophash[i] < 5时,表示存的是状态;
当tophash[i] >= 5时,表示存的是哈希值;

那么问题来了,如果key的哈希值高8位小于minTopHash时,这时候怎么区分是存的状态还是哈希值?

func tophash(hash uintptr) uint8 {
	top := uint8(hash >> (sys.PtrSize*8 - 8))
	if top < minTopHash {
		top  = minTopHash
	}
	return top
}

直接看上面源码,第3-5行可以知道,当计算的哈希值小于minTopHash时,会直接在原有哈希值基础上加上minTopHash,确保哈希值一定大于minTopHash。

emptyRest

这个值有两层意思:一是表示该tophash对应的K/V位置是可用的;二是表示该位置后面的K/V位置都是可用的。

@(伪代码)

if tophash[i] == emptyRest {
	1 keys[i] is empty && values[i] is empty
	2 keys[i 1 .. N-1] is empty && values[i 1 .. N-1] is empty
} 

赋值:

  • 初始化的时,tophash会被置为emptyRest。
  • 删除map元素时,会判断是否需要把删除key对应的tophash置为emptyRest。

作用:

  • 判断bucket是否为空
    当tophash[0]==emptyRest表示整个bucket都是空的,这就是源码里面判断bucket是否为空的方法。

  • 查找时快速判断后面位置是否还需遍历
    如在查找时,在一个bucket中,找到tophash[2]位置,发现值为emptyRest,就可以判断该bucket没有该元素,继续查找下一个bucket。

emptyOne

仅表示该tophash对应的K/V位置是可用的,其后面的是否可用不知道。

赋值:
删除map元素时,会把key对应的tophash先置为emptyOne,再继续判断是否需要置为emptyRest。

evacuatedX && evacuatedY

这两个状态与扩容有关,记录元素被迁移到了新桶的部位–X或Y。如果是等位迁移,旧桶的元素必然被迁移到X部;如果是扩容迁移,旧桶元素可能迁移到X部,也可能迁移到Y部。当迁移到X部时,旧桶tophash置为evacuatedX;当迁移到Y部时,旧桶tophash置为evacuatedY。如下图:
[外链图片转存失败(img-hm1qcNAM-1567759485577)(./1560947852018.png)]

举个例子说明:扩容迁移,要把旧桶1的元素迁到新桶,因为新桶长度增长了一倍,因此旧桶1元素可能被迁移到新桶的1或5。当元素迁移到了1时,把旧桶tophash置为evacuatedX;反之,迁移到了5时,tophash置为evacuatedY。要注意置的是旧桶的tophash。

旧桶迁移完就被回收了,为啥还要记录每个元素迁移的位置?
想了解原因,我们必须要考虑一个很复杂的场景:遍历map时,开始扩容。map遍历并不是原子操作,在遍历过程中会有数据插入、删除等,会导致map扩容。因为遍历发生在扩容前,因此一直是遍历老桶。这时有两种情况:老桶数据没有迁移,这时直接从老桶返回K/V就可以了;老桶数据已经迁移,这时就需要重新查找map。那怎么判断老桶数据是否迁移?这时就用到evacuatedX和evacuatedY,这两个就是用来标记老桶数据迁移状态的。

在map.go的941~966行:

if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) || !(t.reflexivekey() || alg.equal(k, k)) {
	it.key = k
	if t.indirectvalue() {
		v = *((*unsafe.Pointer)(v))
	}
	it.value = v
} else {
	rk, rv := mapaccessK(t, h, k)
	if rk == nil {
		continue // key has been deleted
	}
	it.key = rk
	it.value = rv
}

evacuatedEmpty

当bucket被迁移完时,tophash值置为evacuatedEmpty。

到此这篇关于“Golang map之tophash详解”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Golang map之tophash详解
Golang之map tophash详解
go语言的map结构delete方法源码解读
golang key map 所有_golang之map详解 - 01基础数据结构
go语言的map结构put方法源码解读
由浅入深聊聊Golang的map
golang中map的一些注意事项
深度解密Go语言之 map
golang之map详解
【golang】map原理

上一篇:GOLANG环境安装 下一篇:使用 Go 添加文档
[关闭]
~ ~