浅谈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 Gin实现文件上传下载的示例代码
Apr 02 Golang
Go语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
Go语言中break label与goto label的区别
Apr 28 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
golang 比较浮点数的大小方式
May 02 Golang
go设置多个GOPATH的方式
May 05 Golang
Golang实现AES对称加密的过程详解
May 20 Golang
Go遍历struct,map,slice的实现
Jun 13 Golang
golang 实用库gotable的具体使用
Jul 01 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
GO语言异常处理分析 err接口及defer延迟
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 PDO 查询操作的实例详解
2017/09/23 PHP
PHP xpath()函数讲解
2019/02/11 PHP
JsEasy简介 JsEasy是什么?与下载
2007/03/07 Javascript
类之Prototype.js学习
2007/06/13 Javascript
IE中radio 或checkbox的checked属性初始状态下不能选中显示问题
2009/07/25 Javascript
js 变量类型转换常用函数与代码[比较全]
2009/12/01 Javascript
JQuery 操作/获取table具体代码
2013/06/13 Javascript
js浏览器本地存储store.js介绍及应用
2014/05/13 Javascript
jQuery实现简单网页遮罩层/弹出层效果兼容IE6、IE7
2014/06/16 Javascript
JavaScript实现列表分页功能特效
2015/05/15 Javascript
jquery读取xml文件实现省市县三级联动的方法
2015/05/29 Javascript
javascript实现表单验证
2016/01/29 Javascript
基于Angularjs实现分页功能
2016/05/30 Javascript
Node.js利用js-xlsx处理Excel文件的方法详解
2017/07/05 Javascript
JS简单获得节点元素的方法示例
2018/02/10 Javascript
使用vue2.0创建的项目的步骤方法
2018/09/25 Javascript
微信小程序实现留言板功能
2018/11/02 Javascript
Vue路由守卫之路由独享守卫
2019/09/25 Javascript
vue中实现图片压缩 file文件的方法
2020/05/28 Javascript
基于javascript的无缝滚动动画实现2
2020/08/07 Javascript
在Windows8上的搭建Python和Django环境
2014/07/03 Python
wxpython中Textctrl回车事件无效的解决方法
2016/07/21 Python
python笔记:mysql、redis操作方法
2017/06/28 Python
python中子类调用父类函数的方法示例
2017/08/18 Python
python排序函数sort()与sorted()的区别
2018/09/18 Python
python json.loads兼容单引号数据的方法
2018/12/19 Python
基于django channel实现websocket的聊天室的方法示例
2019/04/11 Python
PyTorch中反卷积的用法详解
2019/12/30 Python
pyCharm 设置调试输出窗口中文显示方式(字符码转换)
2020/06/09 Python
德国Discount-Apotheke中文官网:DC德式康线上药房
2020/02/18 全球购物
世界上最大的皮肤科医生拥有和经营的美容网站:LovelySkin
2021/01/03 全球购物
预备党员入党思想汇报
2014/01/04 职场文书
新浪微博实习心得体会
2014/01/27 职场文书
反对形式主义、官僚主义、享乐主义和奢靡之风整改措施
2014/09/17 职场文书
2014标准社保办理委托书
2014/10/06 职场文书
GO中sync包自由控制并发示例详解
2022/08/05 Golang