浅谈Golang 切片(slice)扩容机制的原理

 更新时间:2021年06月09日 09:07:12   作者:Linux猿  
我们知道 Golang 切片在容量不足的情况下会进行扩容,扩容的原理是怎样的呢,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

我们知道 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 切片扩容机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Go初学者踩坑之go mod init与自定义包的使用

    Go初学者踩坑之go mod init与自定义包的使用

    go mod是go的一个模块管理工具,用来代替传统的GOPATH方案,下面这篇文章主要给大家介绍了关于Go初学者踩坑之go mod init与自定义包的使用,需要的朋友可以参考下
    2022-10-10
  • Go语言题解LeetCode1266访问所有点的最小时间示例

    Go语言题解LeetCode1266访问所有点的最小时间示例

    这篇文章主要为大家介绍了Go语言题解LeetCode1266访问所有点的最小时间示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • golang通用的grpc http基础开发框架使用快速入门

    golang通用的grpc http基础开发框架使用快速入门

    这篇文章主要为大家介绍了golang通用的grpc http基础开发框架使用快速入门详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • 深入学习Golang的流程控制

    深入学习Golang的流程控制

    Go 语言是一门现代化的编程语言,以其简洁高效、并发安全等特点被越来越多的开发者所使用。本文将深入探讨 Go 语言中的流程控制,包括条件语句、循环语句以及控制语句等方面
    2023-05-05
  • goland把go项目打包进docker镜像的全过程记录

    goland把go项目打包进docker镜像的全过程记录

    golang编译的应用是不需要依赖其他运行环境的,下面这篇文章主要给大家介绍了关于goland把go项目打包进docker镜像的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2022-08-08
  • Windows下使用go语言写程序安装配置实例

    Windows下使用go语言写程序安装配置实例

    这篇文章主要介绍了Windows下使用go语言写程序安装配置实例,本文讲解了安装go语言、写go代码、生成可执行文件、批量生成可执行文件等内容,需要的朋友可以参考下
    2015-03-03
  • Go语言sync.Cond使用方法详解

    Go语言sync.Cond使用方法详解

    Go语言标准库中还包含条件变量 sync.Cond,它可以让一组 Goroutine 都在满足特定条件时被唤醒,每一个sync.Cond结构体在初始化时都需要传入一个互斥锁,接下来我们将通过文中例子了解它的使用方法,感兴趣的同学跟着小编一起来看看吧
    2023-07-07
  • go 打包运行文件在windows,liunx运行

    go 打包运行文件在windows,liunx运行

    这篇文章主要介绍了go 打包运行文件在windows,liunx运行的相关资料,需要的朋友可以参考下
    2023-11-11
  • 如何在Go语言中灵活运用匿名函数和闭包

    如何在Go语言中灵活运用匿名函数和闭包

    这篇文章主要为大家介绍了如何在Go语言中灵活运用匿名函数和闭包实现实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Golang logrus 日志包及日志切割的实现

    Golang logrus 日志包及日志切割的实现

    这篇文章主要介绍了Golang logrus 日志包及日志切割的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论