如何扩容
map 增长过程中,对空间的需求也在增加,那么如何完成透明扩容的同时,又不会太多影响性能,就是这里要讨论的。
仅考虑空间增长,map 的扩容方式有两种,overflow
以及 hashGrow
- overflow 是溢出链,是 bucket 级别的扩容,理解成链表
- hashGrow 则是再散列的实现。空间变大的同时,重新进行散列,不过 rehash 过程不是同步,而是被摊还到了
mapassign
以及mapdelete
即 变更 的操作上。同时,在这个过程中也涉及老空间的释放,如何释放的这个部分将放在下面 『如何迁移』里面讨论
overflow
map 存储数据的组织形式是通过 bucket 这个结构来做的。 然后在数据结构中的 h.bucket
是代表一组 bucket,其中的每个 bucket 与对应的 overflow 区域,组成了一个链表。后面用 表头 代表 h.bucket 中的 bucket, 用 bucket 代表 bucket 数据结构。 注意:这个表头不是一个 dummy 节点,也是要用来放数据的。
新的链表节点的增加是通过函数 newoverflow
实现的,主要工作就是先获取空间(可能是预先分配的,也有可能是从系统新申请),然后将其放在链表的末端。(针对 overflow[0] 的管理,目前不是很确定管理条件),对应代码和解释如下。
需要注意的就是:在针对预分配的空间和新申请空间的逻辑是有些不同的
func (h *hmap) newoverflow(t *maptype, b *bmap) *bmap {
var ovf *bmap
if h.extra != nil && h.extra.nextOverflow != nil {
// 如果在预先创建了 overflow 区域,则先从本区域进行获取
ovf = h.extra.nextOverflow
if ovf.overflow(t) == nil {
// overflow() 是读取的该 bucket 最后一个指针空间,是否有值
// 有则代表预先申请的 overflow 区域已经用完,还记得 makeBucketArray 最后的设置吗?
// 是保存的 h.buckets 的起始地址
// 然后 nextOverFlow 维护预申请 overflow 域内的偏移量
h.extra.nextOverflow = (*bmap)(add(unsafe.Pointer(ovf), uintptr(t.bucketsize)))
} else {
// 预先的已经用完了。此时的 ovf 代表了 overflow 内最后一个 bucket,将最后的指针位设置为 空
// 并标记下预先申请的 overflow 区域已经用完
ovf.setoverflow(t, nil)
h.extra.nextOverflow