go 切片长度与容量的区别

简介: go 切片长度与容量的区别

切片的声明

切片可以看成是数组的引用(实际上切片的底层数据结构确实是数组)。在 Go 中,每个数组的大小是固定的,不能随意改变大小,切片可以为数组提供动态增长和缩小的需求,但其本身并不存储任何数据。

// 数组的声明
var a [5]int //只指定长度,元素初始化为默认值0
var a [5]int{1,2,3,4,5}
// 切片的声明
// 方法1:直接初始化
var s []int //声明一个长度和容量为 0 的 nil 切片
var s []int{1,2,3,4,5} // 同时创建一个长度为5的数组
// 方法2:用make()函数来创建切片
var s = make([]int, 0, 5)
// 切分数组:var 变量名 []变量类型 = arr[low, high],low和high为数组的索引。
// 记住规则为:左闭右开
var arr = [5]int{1,2,3,4,5}
var slice []int = arr[1:4] // [2,3,4]

切片的长度和容量

切片的长度是它所包含的元素个数。切片的容量是从它的第一个元素到其底层数组元素末尾的个数。切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s, len(s), cap(s))
// output: [0 1 2 3 4 5 6 7 8 9] 10 10
s1 := s[0:5]
fmt.Println(s1, len(s1), cap(s1))
// output: [0 1 2 3 4] 5 10
s2 := s[5:]
fmt.Println(s2, len(s2), cap(s2))
// output: [5 6 7 8 9] 5 5

切片追加元素后长度和容量的变化

append 函数

Go 提供了内建的 append 函数,为切片追加新的元素。

func append(s []T, vs ...T) []T

append 的返回值是一个包含原切片所有元素加上新添加元素的切片。

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(s, len(s), cap(s))
sResult := append(s, 11)
fmt.Println(sResult, len(sResult), cap(sResult))
// output: 
// [0 1 2 3 4 5 6 7 8 9] 10 10
// [0 1 2 3 4 5 6 7 8 9 11] 11 20

这个时候,我们就可以发现,当我们 append 元素进入切片时,原切片的长度以及容量都发生了变化,但是它们的变化为什么会这样呢?

下面我们一起看看源码是怎么实现的。

切片的源代码学习

Go 中切片的数据结构可以在源码下的 src/runtime/slice.go 中查看。以下源代码基于 go1.16.7 版本。

切片的结构体

切片作为数组的引用,有三个属性字段:指向数组的指针、长度和容量。

type slice struct {
  // 指向底层数组的指针
  array unsafe.Pointer
  // slice 当前元素个数,即 len() 时返回的数
  len   int
  // slice 的容量,即 cap() 时返回的数
  cap   int
}

切片的扩容

slice 通过调用 append 函数来针对slice进行尾部追加元素,如果此时 slicecap 值小于当前 len 加上 append 中传入值的数量,就会调用 runtime.growslice 函数,进行扩容。

我们这里只放出基本的扩容规则的代码解析,如果对内存对齐、数据拷贝等感兴趣,可自行查看对应的源码。

基本扩容规则

func growslice(et *_type, old slice, cap int) slice {
    newcap := old.cap
    doublecap := newcap + newcap
    // 如果新容量大于旧容量的两倍,则直接按照新容量大小申请
    if cap > doublecap {
      newcap = cap
    } else {
        // 如果原有长度小于1024,则新容量是旧容量的2倍
        if old.len < 1024 {
            newcap = doublecap
        } else {
            // 按照原有容量的 1/4 增加,直到满足新容量的需要
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
}

从源码来看,实际上可以整理出几个规则:

  • 当原切片长度小于 1024 时,新的切片长度直接加上 append 元素的个数,容量则会直接 *2
  • 当原切片长度大于等于 1024 时,新的切片长度直接加上 append 元素的个数,容量则会增加 1/4

总结

  • 切片是一个结构体,保存着切片的容量,长度以及指向数组的指针(数组的地址)。
  • 从源码来看,当一个切片进行扩容时,会进行 growslice,这是一个花销较大的操作,在日常开发中,如果能明确知道切片的长度或者容量时,我们需要在初始化的时候声明,避免切片频繁扩容而带来的花销。
相关文章
|
8月前
|
存储 JSON 安全
Go语言切片,使用技巧与避坑指南
Go语言中的切片(Slice)是动态引用数组的高效数据结构,支持扩容与截取。本文从切片基础、常用操作到高级技巧全面解析,涵盖创建方式、`append`扩容机制、共享陷阱及安全复制等内容。通过代码示例详解切片特性,如预分配优化性能、区分`nil`与空切片、处理多维切片等。掌握这些核心知识点,可编写更高效的Go代码。
269 2
|
7月前
|
数据采集 机器学习/深度学习 存储
Go语言实战案例 - 找出切片中的最大值与最小值
本案例通过实现查找整数切片中的最大值与最小值,帮助初学者掌握遍历、比较和错误处理技巧,内容涵盖算法基础、应用场景及完整代码示例,适合初学者提升编程能力。
|
8月前
|
Go 索引
Go语言中使用切片需要注意什么?
本文详细讲解了Go语言中切片(Slice)的使用方法与注意事项。切片是对数组连续片段的引用,具有灵活的操作方式。文章从定义与初始化、长度与容量、自动扩容、共享底层数组、复制、边界检查、零值到拼接等方面展开,并配以示例代码演示。通过学习,读者可深入了解切片的工作原理及优化技巧,避免常见陷阱,提升编程效率与代码质量。
207 2
|
12月前
|
测试技术 Go API
Go 切片导致 rand.Shuffle 产生重复数据的原因与解决方案
在 Go 语言开发中,使用切片时由于其底层数据共享特性,可能会引发意想不到的 Bug。本文分析了 `rand.Shuffle` 后切片数据重复的问题,指出原因在于切片是引用类型,直接赋值会导致底层数组共享,进而影响原始数据。解决方案是使用 `append` 进行数据拷贝,确保独立副本,避免 `rand.Shuffle` 影响原始数据。总结强调了切片作为引用类型的特性及正确处理方法,确保代码稳定性和正确性。
307 82
|
9月前
|
安全 Go 开发者
Go语言之切片的原理与用法 - 《Go语言实战指南》
切片(slice)是Go语言中用于处理变长数据集合的核心结构,基于数组的轻量级抽象,具有灵活高效的特点。切片本质是一个三元组:指向底层数组的指针、长度(len)和容量(cap)。本文详细介绍了切片的声明与初始化方式、基本操作(如访问、修改、遍历)、长度与容量的区别、自动扩容机制、共享与副本处理、引用类型特性以及常见陷阱。通过理解切片的底层原理,开发者可以更高效地使用这一数据结构,优化代码性能。
323 13
|
9月前
|
人工智能 Go
[go]Slice 切片原理
本文详细介绍了Go语言中的切片(slice)数据结构,包括其定义、创建方式、扩容机制及常见操作。切片是一种动态数组,依托底层数组实现,具有灵活的扩容和传递特性。文章解析了切片的内部结构(包含指向底层数组的指针、长度和容量),并探讨了通过`make`创建切片、基于数组生成切片以及切片扩容的规则。此外,还分析了`append`函数的工作原理及其可能引发的扩容问题,以及切片拷贝时需要注意的细节。最后,通过典型面试题深入讲解了切片在函数间传递时的行为特点,帮助读者更好地理解和使用Go语言中的切片。
282 0
|
12月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
317 3
Go 语言入门指南:切片
|
11月前
|
存储 Go
Go中make和new的区别
在 Go 语言中,`make` 和 `new` 都用于分配内存,但功能不同。`make` 用于初始化切片、映射和通道,并返回初始化后的对象;`new` 分配内存并返回指向零值的指针,适用于任何类型。`make` 返回的是数据结构本身,而 `new` 返回指针。`make` 完整初始化特定数据结构,`new` 只初始化为零值。
416 0
|
4月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
277 1
|
6月前
|
Cloud Native 安全 Java
Go:为云原生而生的高效语言
Go:为云原生而生的高效语言
383 1