Go 切片

Slice 实现原理

  • Slice依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重分配并生成新的Slice。接下来按照实际使用场景分别介绍其实现机制

Slice 数据结构

type slice struct {
​    array unsafe.Pointer
​    len   int
​    cap   int
}

使用 make 创建 Slice

slice := make([]int, 5, 10)

使用数组创建 Slice

  • 使用数组来创建Slice时,Slice将与原数组共用一部分内存

  • 切片从数组array[5]开始,到数组array[7]结束(不含array[7]),即切片长度为2,数组后面的内容都作为切片的预留内存,即capacity为5

  • 数组和切片操作可能作用于同一块内存,这也是使用过程中需要注意的地方

slice := array[5:7]

Slice 扩容

  • 使用append向Slice追加元素时,如果Slice空间不足,将会触发Slice扩容,扩容实际上重新一配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;

  • 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍

  • 使用append()向Slice添加一个元素的实现步骤如下:

    • 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice

    • 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice

    • 将新元素追加进新Slice,Slice.len++,返回新的Slice

Slice Copy

  • 使用copy()内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值。

  • 例如长度为10的切片拷贝到长度为5的切片时,将会拷贝5个元素。

  • 也就是说,copy过程中不会发生扩容。

特殊切片

  • 跟据数组或切片生成新的切片一般使用slice := array[start:end]方式,这种新生成的切片并没有指定切片的容量,实际上新切片的容量是从start开始直至array的结束。

  • 比如下面两个切片,长度和容量都是一致的,使用共同的内存地址:

sliceA := make([]int, 5, 10)
sliceB := sliceA[0:5]
  • 根据数组或切片生成切片还有另一种写法,即切片同时也指定容量,即slice[start:end:cap], 其中cap即为新切片的容量,当然容量不能超过原切片实际值,如下所示:

​sliceA := make([]int, 5, 10)  //length = 5; capacity = 10
​sliceB := sliceA[0:5]         //length = 5; capacity = 10
sliceC := sliceA[0:5:5]       //length = 5; capacity = 5

编程Tips

  • 创建切片时可跟据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能;

  • 切片拷贝时需要判断实际拷贝的元素个数

  • 谨慎使用多个切片操作同一个数组,以防读写冲突

Slice总结

  • 每个切片都指向一个底层数组

  • 每个切片都保存了当前切片的长度、底层数组可用容量

  • 使用len()计算切片长度时间复杂度为O(1),不需要遍历切片

  • 使用cap()计算切片容量时间复杂度为O(1),不需要遍历切片

  • 通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而矣

  • 使用append()向切片追加元素时有可能触发扩容,扩容后将会生成新的切片

Last updated

Was this helpful?