浅谈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 interface{}==nil 的几种坑及原理分析
Apr 24 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
浅谈golang 中time.After释放的问题
May 05 Golang
解决golang 关于全局变量的坑
May 06 Golang
基于Go语言构建RESTful API服务
Jul 25 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
golang三种设计模式之简单工厂、方法工厂和抽象工厂
Apr 10 Golang
Golang日志包的使用
Apr 20 Golang
GoFrame框架数据校验之校验结果Error接口对象
Jun 21 Golang
Go语言编译原理之变量捕获
Aug 05 Golang
Go中使用gjson来操作JSON数据的实现
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 xml 入门学习资料
2011/01/01 PHP
php版银联支付接口开发简明教程
2016/10/14 PHP
浅谈PHP中如何实现Hook机制
2017/11/14 PHP
PHP切割汉字的常用方法实例总结
2019/04/27 PHP
学习面向对象之面向对象的基本概念:对象和其他基本要素
2010/11/30 Javascript
JQuery拖拽元素改变大小尺寸实现代码
2012/12/10 Javascript
js工具方法弹出蒙版
2013/05/08 Javascript
nodejs教程 安装express及配置app.js文件的详细步骤
2013/05/11 NodeJs
js简单实现Select互换数据的方法
2015/08/17 Javascript
JS密码生成与强度检测完整实例(附demo源码下载)
2016/04/06 Javascript
jQuery鼠标事件总结
2016/10/13 Javascript
jQuery编写设置和获取颜色的插件
2017/01/09 Javascript
工厂模式在JS中的实践
2017/01/18 Javascript
使用jQuery实现简单的tab框实例
2017/08/22 jQuery
详细介绍RxJS在Angular中的应用
2017/09/23 Javascript
解决JQuery全选/反选第二次失效的问题
2017/10/11 jQuery
详解关于html,css,js三者的加载顺序问题
2019/04/10 Javascript
利用百度echarts实现图表功能简单入门示例【附源码下载】
2019/06/10 Javascript
Django中的“惰性翻译”方法的相关使用
2015/07/27 Python
python 与GO中操作slice,list的方式实例代码
2017/03/20 Python
ubuntu17.4下为python和python3装上pip的方法
2018/06/12 Python
Python爬取数据并写入MySQL数据库的实例
2018/06/21 Python
基于python实现聊天室程序
2018/07/27 Python
python对列进行平移变换的方法(shift)
2019/01/10 Python
Python删除n行后的其他行方法
2019/01/28 Python
Django基础知识 web框架的本质详解
2019/07/18 Python
python 协程中的迭代器,生成器原理及应用实例详解
2019/10/28 Python
django使用JWT保存用户登录信息
2020/04/22 Python
Python实现在线批量美颜功能过程解析
2020/06/10 Python
美国一家专业的太阳镜网上零售商:Solstice太阳镜
2016/07/25 全球购物
什么是符号链接,什么是硬链接?符号链接与硬链接的区别是什么?
2014/01/19 面试题
工艺工程师工作职责
2013/11/23 职场文书
2014年纪委工作总结
2014/12/05 职场文书
css3实现背景图片半透明内容不透明的方法示例
2021/04/13 HTML / CSS
HTML中的表单Form实现居中效果
2021/05/25 HTML / CSS
IDEA2021.2配置docker如何将springboot项目打成镜像一键发布部署
2021/09/25 Java/Android