本文是《Go专家编程》的阅读笔记

1. map的数据结构

Golang底层使用哈希表实现map,哈希表有多个节点,每个节点就一个bucket(桶),每个bucket可以存储最多8对key-value。

  1. type hmap struct {
  2. count int // 哈希表中的元素个数
  3. ...
  4. B int // 当前的负载因子
  5. ...
  6. buckets unsafe.Pointer // 桶数组指针
  7. oldbuckets unsafe.Pointer // 扩容前的桶
  8. ...
  9. }

hmap.buckets的长度是2^B

2. bucket的数据结构

  1. const (
  2. // Maximum number of key/elem pairs a bucket can hold.
  3. bucketCntBits = 3
  4. bucketCnt = 1 << bucketCntBits // 8
  5. )
  6. type bmap struct {
  7. tophash [bucketCnt]uint8 // 存储哈希值的高8位
  8. // 以下两个属性是虚构的,在bmap结构中不存在
  9. data byte[1] // key value数据:key/key/key.../value/value/value
  10. overflow *bmap // 溢出桶的地址
  11. }

每个bucket最多存储bucketCnt=8 个键值对

  • tophash是长度为8的uint8数组,哈希值低位相同的键存入当前bucket时会将哈希值的高位存储在该数组中,以方便后续匹配 。
  • data存放的是key-value数据,存放的顺序是key/key/key…/value/value/value,这样存放能够节省字节对齐带来的空间浪费。
  • overflow是溢出桶的地址,据此将溢出桶形成链表。

3. 哈希冲突

多个哈希值的低位相同命中同一个桶,桶中的键值对超过8个,只能再创建一个键值对存放溢出桶的地址,Golang就是通过这样的链地址法来解决哈希冲突的。

4. 负载因子

负载因子用于描述哈希冲突的情况:
负载因子=键数量/桶数量
哈希表需要将负载因子控制在合适的大小,超过其阀值需要进行rehash,也即键值对重新组织:

  • 负载因子过小,空间利用率低。
  • 负载因子过大,元素存取效率低。过大表示哈希冲突严重,溢出桶形成的链表很长,在链表中查询位置是较慢的。