文章目录
map扩容的源码分析见 下一节
map基本结构
hmap是map的核心数据结构:
type hmap struct {
count int // 当前的元素个数
flags uint8
B uint8 // 桶的数量为2的B次方,方便进行哈希的与运算
noverflow uint16 // 溢出桶的数量
hash0 uint32 // 哈希种子,计算哈希值使用
buckets unsafe.Pointer // 指向当前桶地址
oldbuckets unsafe.Pointer // 扩容时指向旧桶
nevacuate uintptr // 扩容进度计数器
extra *mapextra // 该结构体包含溢出桶位置信息
}
type mapextra struct {
overflow *[]*bmap //指向当前溢出桶首地址
oldoverflow *[]*bmap //指向旧的溢出桶首地址
nextOverflow *bmap //指向下一块可用的溢出桶
}
hmap结构图如下所示,buckets
和oldbuckets
字段指向的都是一块连续的内存区域([ ]bmp)

桶bmap的结构体字段如下
type bmap struct {
tophash [bucketCnt]uint8
}
//编译期间展开如下
type bmap struct{
topbits [8]uint8 //用于表示标志位或hash值高八位来快速定位K/V位置
keys [8]keytype
value [8]valuetype
pad uintptr //此字段go1.16.2版本已删除
overflow uintptr //连接下个bmap溢出桶
}
bmp结构体负责存储key/value值,结构图如下:
hash值定位K/V值
在哈希表中,寻找key/value值过程如下:
- key值会通过哈希函数得到hash值
- hash值与桶数-1(也就是2^B-1)进行与 操作得到所在桶的编号,找到相应的bmp
- 计算hash值高八位,与bmp中的tophash数组一一对比,找到偏移值
- 根据偏移值找到对应的key/value
整个流程如下图所示:
map创建
如mp := make(map[int]int, 20)
代码最终会调用 makemap
函数,此函数负责对hmap结构体进行一系列初始化工作。
func makemap(t *maptype, hint int, h *hmap) *hmap {
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
if h == nil {
h = new(hmap)
}
h.hash0 = fastrand()
B := uint8(0) //2^B为map实际的桶数
for overLoadFactor(hint, B) {
//根据make传入的预设值hint来计算B数值
B++
}
h.B = B //最终2^B数值不会小于hint
if h.B != 0 {
var nextOverflow *bmap
h.buckets, nextOverflow = makeBucketArray(t, h.B, nil) //桶的分配空间初始化操作
if nextOverflow != nil {
//处理有溢出桶的情况,B<4没有溢出桶
h.extra = new(mapextra)
h.extra.nextOverflow = nextOverflow //指针指向第一块溢出桶
}
}
return h
}
计算桶的数量
overLoadFactor
函数根据make传入的预设值来计算B的值
loadFactorNum = 13
loadFactorDen = 2
bucketCntBits = 3
bucketCnt = 1 << bucketCntBits //等价于8
func bucketShift(b uint8) uintptr {
return uintptr(1) << (b & (sys.PtrSize*8 - 1)) //就是返回2的b次方数值
}
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
//等价于:count > 8 && count > 13*(2^B / 2)
//把位置换下,count/2^B > 6.5 6.5也就是哈希表的负载因子
}
申请buckets内存空间
makeBucketArray
函数负责给桶申请一段连续的内存空间
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
base := bucketShift(b) //bucketShift计算2^b的值,base就是桶的总数
nbuckets := base
if b >= 4 {
//大于4才使用溢出桶
nbuckets += bucketShift(b - 4) //溢出桶数量为2^(b-4)
sz := t.bucket.size * nbuckets
up := roundupsize(sz)
if up != sz {
nbuckets = up / t.bucket.size
}
}
if dirtyalloc == nil {