浅谈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 21 Golang
Go缓冲channel和非缓冲channel的区别说明
Apr 25 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
浅谈golang 中time.After释放的问题
May 05 Golang
Golang: 内建容器的用法
May 05 Golang
完美解决golang go get私有仓库的问题
May 05 Golang
Go 自定义package包设置与导入操作
May 06 Golang
go语言中http超时引发的事故解决
Jun 02 Golang
Golang中异常处理机制详解
Jun 08 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
Golang 入门 之url 包
May 04 Golang
go goth封装第三方认证库示例详解
Aug 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获取网络文件的实现代码
2010/01/01 PHP
PHP中运用jQuery的Ajax跨域调用实现代码
2012/02/21 PHP
php输出金字塔的2种实现方法
2014/12/16 PHP
PHP自定义函数格式化json数据示例
2016/09/14 PHP
php加密解密字符串示例
2016/10/13 PHP
php回调函数处理数组操作示例
2020/04/13 PHP
抽出www.templatemonster.com的鼠标悬停加载大图模板的代码
2007/07/11 Javascript
JavaScript自动设置IFrame高度的小例子
2013/06/08 Javascript
通过Ajax使用FormData对象无刷新上传文件方法
2016/12/08 Javascript
微信小程序分页加载的实例代码
2017/07/11 Javascript
解析Vue 2.5的Diff算法
2017/11/28 Javascript
详解js创建对象的几种方法及继承
2019/04/12 Javascript
axios+Vue实现上传文件显示进度功能
2019/04/14 Javascript
vue cli3.0 引入eslint 结合vscode使用
2019/05/27 Javascript
解决vue 表格table列求和的问题
2019/11/06 Javascript
nodejs中内置模块fs,path常见的用法说明
2020/11/07 NodeJs
python实现的登陆Discuz!论坛通用代码分享
2014/07/11 Python
python中的__slots__使用示例
2015/02/26 Python
在cmd命令行里进入和退出Python程序的方法
2018/05/12 Python
python进程和线程用法知识点总结
2019/05/28 Python
Python 占位符的使用方法详解
2019/07/10 Python
python标记语句块使用方法总结
2019/08/05 Python
Python +Selenium解决图片验证码登录或注册问题(推荐)
2020/02/09 Python
pycharm 设置项目的根目录教程
2020/02/12 Python
Python连接HDFS实现文件上传下载及Pandas转换文本文件到CSV操作
2020/06/06 Python
Python 中如何写注释
2020/08/28 Python
Flask处理Web表单的实现方法
2021/01/31 Python
HTML5调用手机发短信和打电话功能
2020/04/29 HTML / CSS
英国受欢迎的运动鞋和街头服装商店:Footasylum
2018/06/12 全球购物
施华洛世奇新加坡官网:SWAROVSKI新加坡
2020/10/06 全球购物
.NET里面什么时候需要调用垃圾回收
2015/06/01 面试题
C#面试问题
2016/07/29 面试题
汽车检测与维修个人求职信
2013/09/24 职场文书
大学生村官工作总结2015
2015/04/09 职场文书
2015年小学二年级班主任工作总结
2015/05/21 职场文书
光荣之路观后感
2015/06/12 职场文书