浅谈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 29 Golang
golang DNS服务器的简单实现操作
Apr 30 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
goland设置颜色和字体的操作
May 05 Golang
解决goland 导入项目后import里的包报红问题
May 06 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
Go语言基础知识点介绍
Jul 04 Golang
golang fmt格式“占位符”的实例用法详解
Jul 04 Golang
入门学习Go的基本语法
Jul 07 Golang
Go中的条件语句Switch示例详解
Aug 23 Golang
golang定时器
Apr 14 Golang
Golang 对es的操作实例
Apr 20 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类
2006/10/09 PHP
10条PHP编程习惯助你找工作
2008/09/29 PHP
php 结果集的分页实现代码
2009/03/10 PHP
php另类上传图片的方法(PHP用Socket上传图片)
2013/10/30 PHP
ThinkPHP视图查询详解
2014/06/30 PHP
PHP提示Warning:phpinfo() has been disabled函数禁用的解决方法
2014/12/17 PHP
ThinkPHP数据操作方法总结
2015/09/28 PHP
学习PHP Cookie处理函数
2016/08/09 PHP
PHP的微信支付接口使用方法讲解
2019/03/08 PHP
JavaScript 动态将数字金额转化为中文大写金额
2009/05/14 Javascript
var与Javascript变量隐式声明
2009/09/17 Javascript
引用 js在IE与FF之间的区别详细解析
2013/11/20 Javascript
IE6-8中Date不支持toISOString的修复方法
2014/05/04 Javascript
基于jQuery实现点击弹出层实例代码
2016/01/01 Javascript
基于Nodejs利用socket.io实现多人聊天室
2017/02/22 NodeJs
js控制文本框禁止输入特殊字符详解
2017/04/07 Javascript
Vue中img的src是动态渲染时不显示的解决
2019/11/14 Javascript
vue实现移动端项目多行文本溢出省略
2020/07/29 Javascript
[20:57]Ti4主赛事第三天开幕式
2014/07/21 DOTA
python通过post提交数据的方法
2015/05/06 Python
在Python中操作字符串之startswith()方法的使用
2015/05/20 Python
Linux下将Python的Django项目部署到Apache服务器
2015/12/24 Python
python多线程并发实例及其优化
2019/06/27 Python
PyTorch之图像和Tensor填充的实例
2019/08/18 Python
关于pytorch处理类别不平衡的问题
2019/12/31 Python
利用OpenCV中对图像数据进行64F和8U转换的方式
2020/06/03 Python
纯css3实现思维导图样式示例
2018/11/01 HTML / CSS
Rentalcars.com中国:世界上最大的在线汽车租赁服务
2019/08/22 全球购物
刘胡兰的英雄事迹材料
2014/02/11 职场文书
乡镇干部个人对照检查材料(群众路线)
2014/09/26 职场文书
考试作弊检讨书怎么写?
2014/12/21 职场文书
工作表扬信
2015/01/17 职场文书
2016年校园植树节广播稿
2015/12/17 职场文书
创业计划书之花店
2019/09/20 职场文书
创业计划书之养殖业
2019/10/11 职场文书
redis cluster支持pipeline的实现思路
2021/06/23 Redis