浅谈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语言求任意类型切片的长度操作
Apr 26 Golang
golang 如何用反射reflect操作结构体
Apr 28 Golang
彻底理解golang中什么是nil
Apr 29 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
Golang之sync.Pool使用详解
May 06 Golang
浅谈golang package中init方法的多处定义及运行顺序问题
May 06 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
详解Go语言Slice作为函数参数的使用
Jul 02 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
Go语言安装并操作redis的go-redis库
Apr 14 Golang
GO语言字符串处理函数之处理Strings包
Apr 14 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和MySql中计算时间差的方法
2011/04/22 PHP
使用PHP进行微信公众平台开发的示例
2015/08/21 PHP
PHP面向对象编程之深入理解方法重载与方法覆盖(多态)
2015/12/24 PHP
thinkPHP5.0框架引入Traits功能实例分析
2017/03/18 PHP
PHP+原生态ajax实现的省市联动功能详解
2017/08/15 PHP
Laravel如何实现适合Api的异常处理响应格式
2020/06/14 PHP
javascript 年月日联动实现核心代码
2009/12/21 Javascript
js select常用操作控制代码
2010/03/16 Javascript
jquery(live)中File input的change方法只起一次作用的解决办法
2011/10/21 Javascript
jquery load事件(callback/data)使用方法及注意事项
2013/02/06 Javascript
关于javascript中dataset的问题小结
2015/11/16 Javascript
如何检测JavaScript的各种类型
2016/07/30 Javascript
Bootstrap模态框(Modal)实现过渡效果
2017/03/17 Javascript
ES6新特性之Symbol类型用法分析
2017/03/31 Javascript
Node.js学习之查询字符串解析querystring详解
2017/09/28 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
2018/02/23 Javascript
Layui给数据表格动态添加一行并跳转到添加行所在页的方法
2018/08/20 Javascript
ES6 更易于继承的类语法的使用
2019/02/11 Javascript
详解vue父子组件关于模态框状态的绑定方案
2019/06/05 Javascript
Layui实现带查询条件的分页
2019/07/27 Javascript
不刷新网页就能链接新的js文件方法总结
2020/03/01 Javascript
[50:28]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 Newbee vs KG
2018/04/01 DOTA
python 实现归并排序算法
2012/06/05 Python
Python的gevent框架的入门教程
2015/04/29 Python
python开发之list操作实例分析
2016/02/22 Python
Python 遍历子文件和所有子文件夹的代码实例
2016/12/21 Python
Python日期的加减等操作的示例
2017/08/15 Python
Python初学者常见错误详解
2019/07/02 Python
pycharm通过ssh连接远程服务器教程
2020/02/12 Python
Django实现whoosh搜索引擎使用jieba分词
2020/04/08 Python
canvas仿写贝塞尔曲线的示例代码
2017/12/29 HTML / CSS
台湾饭店和机票预订网站:Expedia台湾
2016/08/05 全球购物
前台文员我鉴定
2014/01/12 职场文书
再婚婚前财产协议书范本
2014/10/19 职场文书
人事主管岗位职责
2015/02/04 职场文书
装修安全责任协议书
2016/03/22 职场文书