浅谈Golang 切片(slice)扩容机制的原理


Posted in Golang onJune 09, 2021

我们知道 Golang 切片(slice) 在容量不足的情况下会进行扩容,扩容的原理是怎样的呢?是不是每次扩一倍?下面我们结合源码来告诉你答案。

一、源码

Version : go1.15.6  src/runtime/slice.go

//go1.15.6 源码 src/runtime/slice.go
func growslice(et *_type, old slice, cap int) slice {
 //省略部分判断代码
    //计算扩容部分
    //其中,cap : 所需容量,newcap : 最终申请容量
 newcap := old.cap
 doublecap := newcap + newcap
 if cap > doublecap {
  newcap = cap
 } else {
  if old.len < 1024 {
   newcap = doublecap
  } else {
   // Check 0 < newcap to detect overflow
   // and prevent an infinite loop.
   for 0 < newcap && newcap < cap {
    newcap += newcap / 4
   }
   // Set newcap to the requested cap when
   // the newcap calculation overflowed.
   if newcap <= 0 {
    newcap = cap
   }
  }
 } 
 //省略部分判断代码
}

二、原理

1. 如果当前所需容量 (cap) 大于原先容量的两倍 (doublecap),则最终申请容量(newcap)为当前所需容量(cap);

2. 如果<条件1>不满足,表示当前所需容量(cap)不大于原容量的两倍(doublecap),则进行如下判断;

3. 如果原切片长度(old.len)小于1024,则最终申请容量(newcap)等于原容量的两倍(doublecap);

4. 否则,最终申请容量(newcap,初始值等于 old.cap)每次增加 newcap/4,直到大于所需容量(cap)为止,然后,判断最终申请容量(newcap)是否溢出,如果溢出,最终申请容量(newcap)等于所需容量(cap);

这样说大家可能不太明白,来几个例子:

2.1 实例1

验证条件1:

package main
 
import "fmt"
 
func main() {
 //第1条中的例子:
 var slice = []int{1, 2, 3}
 var slice1 = []int{4, 5, 6, 7, 8, 9, 10, 11, 12}
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
 fmt.Printf("slice1 %v len = %v cap = %v\n", slice1, len(slice1), cap(slice1))
 slice = append(slice, slice1...)
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
}

输出:

[root@localhost test]# go run main.go
slice [1 2 3] len = 3 cap = 3
slice1 [4 5 6 7 8 9 10 11 12] len = 9 cap = 9
slice [1 2 3 4 5 6 7 8 9 10 11 12] len = 12 cap = 12
[root@localhost test]#

在实例1中,所需容量 cap = 9+3 = 12,原容量的两倍 doublecap = 2 * 3 = 6,满足 <条件1> 即:所需容量大于原容量的两倍,所以最终申请容量 newcap = cap = 12。

2.2 实例2

验证条件2,3:

package main
import "fmt"
 
func main() {
 //第2、3条中的例子:
 var slice = []int{1, 2, 3, 4, 5, 6, 7}
 var slice1 = []int{8, 9}
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
 fmt.Printf("slice1 %v len = %v cap = %v\n", slice1, len(slice1), cap(slice1))
 slice = append(slice, slice1...)
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
}

 输出:

[root@localhost test]# go run main.go
slice [1 2 3 4 5 6 7] len = 7 cap = 7
slice1 [8 9] len = 2 cap = 2
slice [1 2 3 4 5 6 7 8 9] len = 9 cap = 14
[root@localhost test]#

在实例2中,所需容量 cap = 7+2 = 9,原容量的两倍 doublecap = 2*7 = 14,原切片长度 old.len = 7,满足 <条件2,3>,即: 所需容量小于原容量的两倍,并且原切片长度 old.len 小于1024,所以,最终申请容量 newcap = doublecap = 14。

2.3 实例3

验证条件4:

package main
import "fmt"
 
func main() {
 //第2条中的例子:
 var slice []int
 for i := 0; i < 1024; i++ {
  slice = append(slice, i)
 }
 var slice1 = []int{1024, 1025}
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
 fmt.Printf("slice1 %v len = %v cap = %v\n", slice1, len(slice1), cap(slice1))
 slice = append(slice, slice1...)
 fmt.Printf("slice %v len = %v cap = %v\n", slice, len(slice), cap(slice))
}

输出:

[root@localhost test]# go run main.go
slice [0 1 2 3 4 5 6……1017 1018 1019 1020 1021 1022 1023] len = 1024 cap = 1024
slice1 [1024 1025] len = 2 cap = 2
slice [0 1 2 3 4 5 6……1017 1018 1019 1020 1021 1022 1023 1024 1025] len = 1026 cap = 1280
[root@localhost test]#

在实例3中,所需容量 cap = 1024+2 = 1026,doublecap = 2048,  old.len = 1024,满足 <条件4> ,所以,newcap = 1024 + 1024/4 = 1280。

到此这篇关于浅谈Golang 切片(slice)扩容机制的原理的文章就介绍到这了,更多相关Golang 切片扩容机制内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Golang 相关文章推荐
go:垃圾回收GC触发条件详解
Apr 24 Golang
golang正则之命名分组方式
Apr 25 Golang
浅谈Golang 嵌套 interface 的赋值问题
Apr 29 Golang
golang import自定义包方式
Apr 29 Golang
Go 在 MongoDB 中常用查询与修改的操作
May 07 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
详解Golang如何优雅的终止一个服务
Mar 21 Golang
golang三种设计模式之简单工厂、方法工厂和抽象工厂
Apr 10 Golang
Golang数据类型和相互转换
Apr 12 Golang
golang使用map实现去除重复数组
Apr 14 Golang
Golang Elasticsearches 批量修改查询及发送MQ
Apr 19 Golang
Golang 入门 之url 包
May 04 Golang
Golang中异常处理机制详解
Go语言实现Snowflake雪花算法
Jun 08 #Golang
go语言中http超时引发的事故解决
Jun 02 #Golang
Golang二维数组的使用方式
May 28 #Golang
Golang标准库syscall详解(什么是系统调用)
May 25 #Golang
go 实现简易端口扫描的示例
May 22 #Golang
go xorm框架的使用
May 22 #Golang
You might like
php正则表达式使用的详细介绍
2013/04/27 PHP
PHP+FFMPEG实现将视频自动转码成H264标准Mp4文件
2014/09/24 PHP
php文件操作相关类实例
2015/06/18 PHP
PHP批量获取网页中所有固定种子链接的方法
2016/11/18 PHP
PHP实现的62进制转10进制,10进制转62进制函数示例
2019/06/06 PHP
laravel5.1框架基础之Blade模板继承简单使用方法分析
2019/09/05 PHP
js 格式化时间日期函数小结
2010/03/20 Javascript
jQueryUI如何自定义组件实现代码
2010/11/14 Javascript
javascript闭包的高级使用方法实例
2013/07/04 Javascript
关于JS中的闭包浅谈
2013/08/23 Javascript
js实现仿爱微网两级导航菜单效果代码
2015/08/31 Javascript
JavaScript实现图片自动加载的瀑布流效果
2016/04/11 Javascript
JS对象创建的几种方式整理
2017/02/28 Javascript
vue项目中v-model父子组件通信的实现详解
2017/12/10 Javascript
js中bool值的转换及“&amp;&amp;”、“||”、 “!!”详解
2017/12/21 Javascript
BootStrap modal实现拖拽功能
2018/12/01 Javascript
vue-router之实现导航切换过渡动画效果
2019/10/31 Javascript
vue+ElementUI 关闭对话框清空验证,清除form表单的操作
2020/08/06 Javascript
在vue项目中 实现定义全局变量 全局函数操作
2020/10/26 Javascript
[03:14]DOTA2斧王 英雄基础教程
2013/11/26 DOTA
[45:38]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#1Liquid VS Alliance第一局
2016/03/02 DOTA
Pycharm学习教程(4) Python解释器的相关配置
2017/05/03 Python
Python爬虫实现全国失信被执行人名单查询功能示例
2018/05/03 Python
Python实现迭代时使用索引的方法示例
2018/06/05 Python
对Python中数组的几种使用方法总结
2018/06/28 Python
python编写计算器功能
2019/10/25 Python
python实现大战外星人小游戏实例代码
2019/12/26 Python
澳大利亚顶级美发和美容贸易超市:glamaCo
2020/01/19 全球购物
什么是重载?CTS、CLS和CLR分别做何解释
2012/05/06 面试题
祖国在我心中演讲稿400字
2014/05/04 职场文书
2014预备党员批评与自我批评思想汇报
2014/09/20 职场文书
快消品行业营销模式与盈利模式分享
2019/09/27 职场文书
vue-cropper插件实现图片截取上传组件封装
2021/05/27 Vue.js
SQL实现LeetCode(177.第N高薪水)
2021/08/04 MySQL
vue如何使用模拟的json数据查看效果
2022/03/31 Vue.js
Python必备技巧之函数的使用详解
2022/04/04 Python