浅谈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中semaphore(信号量)源码
Apr 03 Golang
win10下go mod配置方式
Apr 25 Golang
go语言求任意类型切片的长度操作
Apr 26 Golang
Golang 空map和未初始化map的注意事项说明
Apr 29 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
Golang二维数组的使用方式
May 28 Golang
golang fmt格式“占位符”的实例用法详解
Jul 04 Golang
golang内置函数len的小技巧
Jul 25 Golang
Go Plugins插件的实现方式
Aug 07 Golang
golang操作redis的客户端包有多个比如redigo、go-redis
Apr 14 Golang
Go gRPC进阶教程gRPC转换HTTP
Jun 16 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 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/11/25 PHP
PHP中strlen()和mb_strlen()的区别浅析
2014/06/19 PHP
thinkPHP5.0框架自动加载机制分析
2017/03/18 PHP
PHP 7.4中使用预加载的方法详解
2019/07/08 PHP
php的无刷新操作实现方法分析
2020/02/28 PHP
Javascript MVC框架Backbone.js详解
2014/09/18 Javascript
jQuery实现可编辑表格并生成json结果(实例代码)
2017/07/19 jQuery
JavaScript 基础表单验证示例(纯Js实现)
2017/07/20 Javascript
node.js 用socket实现聊天的示例代码
2017/10/17 Javascript
深入理解NodeJS 多进程和集群
2018/10/17 NodeJs
解决vue单页面应用中动态修改title问题
2019/06/09 Javascript
JS事件流与事件处理程序实例分析
2019/08/16 Javascript
js实现点击图片在屏幕中间弹出放大效果
2019/09/11 Javascript
webpack的 rquire.context用法实现工程自动化的方法
2020/02/07 Javascript
[02:15]2015国际邀请赛选手档案IG.Ferrari 430
2015/07/30 DOTA
Python 字符串中的字符倒转
2008/09/06 Python
在Linux上安装Python的Flask框架和创建第一个app实例的教程
2015/03/30 Python
20招让你的Python飞起来!
2016/09/27 Python
Python版名片管理系统
2018/11/30 Python
pyshp创建shp点文件的方法
2018/12/31 Python
解决新版Pycharm中Matplotlib图像不在弹出独立的显示窗口问题
2019/01/15 Python
Django 自定义404 500等错误页面的实现
2020/03/08 Python
Django-simple-captcha验证码包使用方法详解
2020/11/28 Python
基于HTML5 WebGL的3D机房的示例
2018/03/16 HTML / CSS
HTML5 图片预加载的示例代码
2020/03/25 HTML / CSS
欧洲最大的婴幼儿服装及内衣公司:Petit Bateau(小帆船)
2016/08/16 全球购物
财务管理专业应届毕业生求职信
2013/09/22 职场文书
财务人员的自我评价范文
2014/03/03 职场文书
股权投资意向书
2014/04/01 职场文书
辞旧迎新演讲稿
2014/09/15 职场文书
2014年技术部工作总结
2014/12/12 职场文书
2015年小学数学教研组工作总结
2015/05/21 职场文书
办公室日常管理制度
2015/08/04 职场文书
golang 实现菜单树的生成方式
2021/04/28 Golang
Python包管理工具pip的15 个使用小技巧
2021/05/17 Python
python中pandas对多列进行分组统计的实现
2021/06/18 Python