大家好,在上篇文章hash 表在 go 语言中的实现中介绍了下golang中map的数据结构以及底层的存储逻辑。 在介绍数据结构的时候,其中hmap中有一个重要的字段:B。我们知道B值是用来确定buckets数组大小的。那么,在用make初始化一个map的时候,B值是怎么计算的呢?本文就来介绍下B值的计算逻辑。

什么是负载因子

负载因子是衡量hash表中当前空间占用率的指标。在go中,就是平均每个bucket存储的元素个数。

计算公式如下: LoadFactor(负载因子)= hash表中已存储的键值对的总数量/hash桶的个数(即hmap结构中buckets数组的个数)

在各语言的实现中,都会确定一个负载因子的阈值,当负载因子超过这个阈值时,就要进行扩容,以平衡存储空间和查找元素时的性能。

以下是go语言中给出各负载因子下的测试表。

loadFactor%overflowbytes/entryhitprobemissprobe
4.002.1320.773.004.00
4.504.0517.303.254.50
5.006.8514.773.505.00
5.5010.5512.943.755.50
6.0015.2711.674.006.00
6.5020.9010.794.256.50
7.0027.1410.154.507.00
7.5034.039.734.757.50
8.0041.109.405.008.00
  • %overflow = percentage of buckets which have an overflow bucket
  • bytes/entry = overhead bytes used per key/elem pair
  • hitprobe = # of entries to check when looking up a present key
  • missprobe = # of entries to check when looking up an absent key

根据上面的测试数据,go中map的负载因子被硬性的定为了 6.5,即平均每个bucket存储的key/value对大于等于6.5个的时候,就会进行扩容。

hmap中的B值的初始化计算

初始化map空间的时候,我们通过make可以指定元素的个数.如下,初始化一个能包含16个元素大小的map:

m := make(map[string]int, 16)

那么,在hmap中的B值是如何计算的呢? 我们由上一篇文章可知,在hmap中,buckets数组的元素数=2^B次方。 map的负载因子≥6.5时会自动扩容。 当前map的key/value元素数量为16。那计算B就变成了以下逻辑:

元素个数为16的情况下,分配几个bucket才能满足负载因子<6.5

即以下公式:

元素个数/bucket数量 ≤ 6.5

进一步演变成以下公式

元素个数 ≤ bucket数量*6.5

将bucket数量=2^B次方带入以上公式,则最终的公式为:

初始元素个数 ≤ 2^B * 6.5

当初始元素个数为16时,上述公式为:

16 ≤ 2^B * 6.5

那么,让B从0开始依次递增,直到遇到让该公式成立的最小B值即可。 下面咱们一起来推导:

B值不等式结果
016≤ 2^0 * 6.5 => 16 ≤ 6.5不成立,B递增1
116 ≤ 2^1 * 6.5 => 16 ≤ 13不成立,B递增1
216 ≤ 2^2 * 6.5 => 16 ≤ 26成立,B结束递增

由此可知,当初始元素个数为16时,B为2,则计算出bucket的数量为2^2次方,为4个bucket。

下面来看看go的源代码的实现:

通过源代码我们可知在makemap函数中,计算B的代码如下:

B := uint8(0)
for overLoadFactor(hint, B) {
    B++
}
h.B = B

而 overLoadFactor函数如下

func overLoadFactor(count int, B uint8) bool {
    return count > bucketCnt && uintptr(count) > loadFactorNum * (bucketShift(B)/loadFactorDen)
}

再来看bucketShift函数的实现:

func bucketShift(b uint8) uintptr {
    return uintptr(1) << (b && (sy 1.PtrSize*8 -1))
}

总结

根据golang团队的测试数据,将负载因子定为了6.5,即平均每个bucket保存的元素≥6.5个时会触发扩容。所以,在初始化map时,元素个数确定的情况下,计算B值,就转变成至少分配几个bucket,能使当前的负载因子不超过6.5。再根据buckets数组的个数=2^B次方计算可得B值。