浅谈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 相关文章推荐
golang 实现两个结构体复制字段
Apr 28 Golang
彻底理解golang中什么是nil
Apr 29 Golang
Golang 使用Map实现去重与set的功能操作
Apr 29 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
解决Goland 同一个package中函数互相调用的问题
May 06 Golang
Golang标准库syscall详解(什么是系统调用)
May 25 Golang
golang fmt格式“占位符”的实例用法详解
Jul 04 Golang
golang生成vcf通讯录格式文件详情
Mar 25 Golang
Go语言的协程上下文的几个方法和用法
Apr 11 Golang
Golang 遍历二叉树
Apr 19 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
基于Python实现西西成语接龙小助手
Aug 05 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源代码加密?PHP二进制加密与解密的解决办法
2013/04/22 PHP
详解PHP归并排序的实现
2016/10/18 PHP
PHP中的函数声明与使用详解
2017/05/27 PHP
PHP7 foreach() 函数修改
2021/03/09 PHP
Google排名中的10个最著名的 JavaScript库
2010/04/27 Javascript
Dom操作之兼容技巧分享
2011/09/20 Javascript
javascript快速排序算法详解
2014/09/17 Javascript
jQuery中slideUp 和 slideDown 的点击事件
2015/02/26 Javascript
跟我学习javascript的基本类型和引用类型
2015/11/16 Javascript
BootStrap 智能表单实战系列(五) 表单依赖插件处理
2016/06/13 Javascript
jQuery 3.0十大新特性
2016/07/06 Javascript
angular源码学习第一篇 setupModuleLoader方法
2016/10/20 Javascript
详解handlebars+require基本使用方法
2016/12/21 Javascript
AngularJS 防止页面闪烁的方法
2017/03/09 Javascript
详解Vue整合axios的实例代码
2017/06/21 Javascript
利用three.js画一个3D立体的正方体示例代码
2017/11/19 Javascript
jQuery实现获取form表单内容及绑定数据到form表单操作分析
2018/07/03 jQuery
JavaScript相等运算符的九条规则示例详解
2019/10/20 Javascript
简单了解常用的JavaScript 库
2020/07/16 Javascript
javascript实现倒计时提示框
2021/03/02 Javascript
[59:08]Ti4 冒泡赛第二天 NEWBEE vs Titan 2
2014/07/15 DOTA
python使用递归解决全排列数字示例
2014/02/11 Python
bpython 功能强大的Python shell
2016/02/16 Python
python3+PyQt5泛型委托详解
2018/04/24 Python
Python中创建二维数组
2018/10/17 Python
Python面向对象程序设计之类的定义与继承简单示例
2019/03/18 Python
TensorFlow设置日志级别的几种方式小结
2020/02/04 Python
tensorflow 实现数据类型转换
2020/02/17 Python
利用python汇总统计多张Excel
2020/09/22 Python
Maje德国官网:法国女性成衣品牌
2017/02/10 全球购物
几道数据库的概念性面试题
2014/05/30 面试题
专科生就业求职信
2014/06/22 职场文书
2015年财务工作总结范文
2015/03/31 职场文书
详解Vue的sync修饰符
2021/05/15 Vue.js
linux中nohup和后台运行进程查看及终止
2021/06/24 Python
python入门学习关于for else的特殊特性讲解
2021/11/20 Python