Golang中map缩容的实现

 更新时间:2025年03月04日 11:24:00   作者:李若盛开  
本文主要介绍了Go语言中map的扩缩容机制,包括grow和hashGrow方法的处理,具有一定的参考价值,感兴趣的可以了解一下

基本分析

在 Go 底层源码 src/runtime/map.go 中,扩缩容的处理方法是 grow 为前缀的方法来处理的。

其中扩缩容涉及到的是插入元素的操作,对应 mapassign 方法:

func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
  ...
 if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
  hashGrow(t, h)
  goto again
 }
  ...
}
func (h *hmap) growing() bool {
 return h.oldbuckets != nil
}
func overLoadFactor(count int, B uint8) bool {
 return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
func tooManyOverflowBuckets(noverflow uint16, B uint8) bool {
 if B > 15 {
  B = 15
 }
 return noverflow >= uint16(1)<<(B&15)
} 

核心看到针对扩缩容的判断逻辑:

当前没有在扩容:条件为 oldbuckets 不为 nil。

是否可以进行扩容:条件为 hmap.count> hash 桶数量 (2^B)*6.5。其中 hmap.count 指的是map 的数据数目, 2^B 仅指 hash 数组的大小,不包含溢出桶。

是否可以进行缩容:条件为溢出桶(noverflow)的数量 >= 32768(1<<15)。

可以关注到,无论是扩容还是缩容,其都是由 hashGrow 方法进行处理:

 func hashGrow(t *maptype, h *hmap) {
 bigger := uint8(1)
 if !overLoadFactor(h.count+1, h.B) {
  bigger = 0
  h.flags |= sameSizeGrow
 }
  ...
}

若是扩容,则 bigger 为 1,也就是 B+1。代表 hash 表容量扩大 1 倍。不满足就是缩容,也就是 hash 表容量不变。

可以得出结论:map 的扩缩容的主要区别在于 hmap.B 的容量大小改变。而缩容由于 hmap.B 压根没变,内存空间的占用也是没有变化的。

带来的隐患

这种方式其实是存在运行隐患的,也就是导致在删除元素时,并不会释放内存,使得分配的总内存不断增加。如果一个不小心,拿 map 来做大 key/value 的存储,也不注意管理,很容易就内存爆了。

也就是 Go 语言的 map 目前实现的是 “伪缩容”,仅针对溢出桶过多的情况。若是触发缩容,hash 数组的占用的内存大小不变(等量扩容)。

若要实现 ”真缩容“,Go Contributor @josharian 表示目前唯一可用的解决方法是:创建一个新的 map 并从旧的 map 中复制元素。

示例如下:

old := make(map[int]int, 9999999)
new := make(map[int]int, len(old))
for k, v := range old {
    new[k] = v
}
old = new
...

为什么不支持缩容

下述内容会主要基于如下两个 issues 和 proposal 来分析:

《runtime: shrink map as elements are deleted[1]》
《proposal: runtime: add way to clear and reuse a map's working storage[2]》

目前 map 的缩容处理起来比较棘手,最早的 issues 是 2016 年提出的,也有人提过一些提案,但都因为种种原因被拒绝了。

简单来讲,就是没有找到一个很好的方法实现,存在明确的实现成本问题,没办法很方便的 ”告诉“ Go 运行时,我要:

  • 记得保留存储空间,我要立即重用 map。
  • 赶紧释放存储空间,map 从现在开始会小很多。

抽象来看症结是:需要保证增长结果在下一个开始之前完成,此处的增长指的是 ”从小到大,从一个大小到相同大小,从大到小“ 的复杂过程。

这属于一个多重 case,从而导致也就一直拖着,慢慢想。

到此这篇关于Golang中map缩容的实现的文章就介绍到这了,更多相关Golang map缩容 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go函数的使用示例教程

    Go函数的使用示例教程

    这篇文章主要介绍了Go函数的使用示例,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-07-07
  • Go语言极简入门之语法、并发及避坑全攻略

    Go语言极简入门之语法、并发及避坑全攻略

    Go是由谷歌支持的开源编程语言,属于编译型语言,对并发编程有较好的支持,这篇文章主要介绍了Go语言极简入门之语法、并发及避坑的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2026-06-06
  • 如何判断Golang接口是否实现的操作

    如何判断Golang接口是否实现的操作

    这篇文章主要介绍了如何判断Golang接口是否实现的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go语言omitempty选项的实现

    Go语言omitempty选项的实现

    本文主要介绍了Go语言omitempty选项的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • go语言实现字符串与其它类型转换(strconv包)

    go语言实现字符串与其它类型转换(strconv包)

    strconv包是Go语言标准库的一部分,主要提供字符串与基本数据类型之间的转换功能,使用strconv包可以方便地在不同类型之间进行转换,满足日常编程中的需求,感兴趣的可以了解一下
    2024-10-10
  • go语言中time包的各种函数总结

    go语言中time包的各种函数总结

    时间和日期是我们编程中经常会用到的,下面这篇文章主要给大家介绍了关于go语言中time包的各种函数总结的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Go语言中gPRC的使用

    Go语言中gPRC的使用

    本文主要介绍了Go语言中gPRC的使用,包括Protobuf定义服务接口、HTTP/2协议与性能优势,以及流模式和发布-订阅系统的实现,具有一定的参考价值,感兴趣的可以了解一下
    2025-07-07
  • Go 连接 MySQL之 MySQL 预处理详解

    Go 连接 MySQL之 MySQL 预处理详解

    Go语言提供了丰富的库和工具,可以方便地连接MySQL数据库。MySQL预处理是一种提高数据库操作效率和安全性的技术。Go语言中的第三方库提供了MySQL预处理的支持,通过使用预处理语句,可以避免SQL注入攻击,并且可以提高数据库操作的效率。
    2023-06-06
  • Go中的gRPC入门教程详解

    Go中的gRPC入门教程详解

    本文详细讲解了Go中的gRPC入门教程,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-03-03
  • Go语言net包RPC远程调用三种方式http与json-rpc及tcp

    Go语言net包RPC远程调用三种方式http与json-rpc及tcp

    这篇文章主要为大家介绍了Go语言net包RPC远程调用三种方式分别使用http与json-rpc及tcp的示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助
    2021-11-11

最新评论