浅谈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语言切片前或中间插入项与内置copy()函数详解
Apr 27 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
golang 实现并发求和
May 08 Golang
go web 预防跨站脚本的实现方式
Jun 11 Golang
试了下Golang实现try catch的方法
Jul 01 Golang
go goroutine 怎样进行错误处理
Jul 16 Golang
Golang 并发下的问题定位及解决方案
Mar 16 Golang
如何解决goland,idea全局搜索快捷键失效问题
Apr 03 Golang
Golang 字符串的常见操作
Apr 19 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 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设计模式之观察者模式(Observer)详细介绍和代码实例
2014/04/08 PHP
合并ThinkPHP配置文件以消除代码冗余的实现方法
2014/07/22 PHP
VPS中使用LNMP安装WordPress教程
2014/12/28 PHP
php动态生成缩略图并输出显示的方法
2015/04/20 PHP
使用SyntaxHighlighter实现HTML高亮显示代码的方法
2010/02/04 Javascript
父子窗体间传递JSON格式的数据的代码
2010/12/25 Javascript
用js写了一个类似php的print_r输出换行功能
2013/02/18 Javascript
javascript日期格式化示例分享
2014/03/05 Javascript
js中的事件捕捉模型与冒泡模型实例分析
2015/01/10 Javascript
Js可拖拽放大的层拖动特效实现方法
2015/02/25 Javascript
jquery实现的省市区三级联动
2015/04/02 Javascript
浅析JavaScript访问对象属性和方法及区别
2015/11/16 Javascript
js+css简单实现网页换肤效果
2015/12/29 Javascript
详解Bootstrap创建表单的三种格式(一)
2016/01/04 Javascript
jQuery Mobile开发中日期插件Mobiscroll使用说明
2016/03/02 Javascript
JavaScript高级程序设计(第三版)学习笔记6、7章
2016/03/11 Javascript
AngularJS 中的Promise --- $q服务详解
2016/09/14 Javascript
Vue + Webpack + Vue-loader学习教程之相关配置篇
2017/03/14 Javascript
利用Jasmine对Angular进行单元测试的方法详解
2017/06/12 Javascript
node.js 发布订阅模式的实例
2017/09/10 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
layer ui 导入文件之前传入数据的实例
2019/09/23 Javascript
js实现随机div颜色位置 类似满天星效果
2019/10/24 Javascript
JS实现简单日历特效
2020/01/03 Javascript
VUE项目axios请求头更改Content-Type操作
2020/07/24 Javascript
Python实现压缩和解压缩ZIP文件的方法分析
2017/09/28 Python
Python3之简单搭建自带服务器的实例讲解
2018/06/04 Python
python opencv 实现对图像边缘扩充
2020/01/19 Python
在pycharm中为项目导入anacodna环境的操作方法
2020/02/12 Python
英国经典球衣网站:Classic Football Shirts
2017/05/20 全球购物
2014庆六一活动方案
2014/03/02 职场文书
拾金不昧锦旗标语
2014/06/27 职场文书
市委召开党的群众路线教育实践活动总结大会报告
2014/10/21 职场文书
先进个人事迹材料
2014/12/29 职场文书
英语感谢信范文
2015/01/20 职场文书
一文搞懂Redis中String数据类型
2022/04/03 Redis