浅谈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 25 Golang
golang中的空slice案例
Apr 27 Golang
Go语言中break label与goto label的区别
Apr 28 Golang
goland设置颜色和字体的操作
May 05 Golang
Go标准容器之Ring的使用说明
May 05 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang全局变量加锁的问题解决
May 08 Golang
Go语言应该什么情况使用指针
Jul 25 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
Golang bufio详细讲解
Apr 21 Golang
Golang 切片(Slice)实现增删改查
Apr 22 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 开源AJAX框架14种
2009/08/24 PHP
php生成txt文件标题及内容的方法
2014/01/16 PHP
php+ajax导入大数据时产生的问题处理
2014/06/11 PHP
thinkphp的URL路由规则与配置实例
2014/11/26 PHP
浅谈PHP中其他类型转化为Bool类型
2016/03/28 PHP
phpstudy的php版本自由修改的方法
2017/10/18 PHP
PHP连接及操作PostgreSQL数据库的方法详解
2019/01/30 PHP
Linux下安装Memcached服务器和客户端与PHP使用示例
2019/04/15 PHP
基于Jquery的文字滚动跑马灯插件(一个页面多个滚动区)
2010/07/26 Javascript
不同的jQuery API来处理不同的浏览器事件
2012/12/09 Javascript
jquery向上向下取整适合分页查询
2014/09/06 Javascript
ECMAScript6中Map/WeakMap详解
2015/06/12 Javascript
JS基于MSClass和setInterval实现ajax定时采集信息并滚动显示的方法
2016/04/18 Javascript
Vue.js每天必学之Class与样式绑定
2016/09/05 Javascript
Javascript中 带名 匿名 箭头函数的重要区别(推荐)
2017/01/29 Javascript
VUE axios发送跨域请求需要注意的问题
2017/07/06 Javascript
解决Vue 项目打包后favicon无法正常显示的问题
2018/09/01 Javascript
在axios中使用params传参的时候传入数组的方法
2018/09/25 Javascript
模块化react-router配置方法详解
2019/06/03 Javascript
layui禁用侧边导航栏点击事件的解决方法
2019/09/25 Javascript
Vue组件模板及组件互相引用代码实例
2020/03/11 Javascript
Python设置默认编码为utf8的方法
2016/07/01 Python
Python对excel文档的操作方法详解
2018/12/10 Python
Python 3.6 中使用pdfminer解析pdf文件的实现
2019/09/25 Python
python实现图像拼接功能
2020/03/23 Python
python实现贪吃蛇双人大战
2020/04/18 Python
Python日志处理模块logging用法解析
2020/05/19 Python
Python3爬虫中关于Ajax分析方法的总结
2020/07/10 Python
python读取excel数据并且画图的实现示例
2021/02/08 Python
英国高端食品和葡萄酒超市:Waitrose
2016/08/23 全球购物
大学四年规划书范文
2013/12/27 职场文书
大学生的网上创业计划书
2013/12/31 职场文书
小学学雷锋活动总结
2014/04/25 职场文书
大学生新学期计划书
2014/04/28 职场文书
nginx配置文件使用环境变量的操作方法
2021/06/02 Servers
Android Studio实现简易进制转换计算器
2022/05/20 Java/Android