Slice在使用过程中需要懂的一些数据结构
源码包中 src/runtime/slice.go:slice 定义了 Slice 的数据结构,从数据结构看 Slice 很清晰,array 指针指向底层数组,len 表示切片长度,cap 表示底层数组容量:
type slice struct {
array unsafe.Pointer
len int
cap int
}
下图使用 make 来创建 Slice 时,可以同时指定长度和容量,创建时底层会分配一个数组,数组的长度即容量。 例如,语句 slice := make ([] int, 5, 10) 所创建的 Slice,结构所示:
- 上图可以理解 Slice 长度为 5,即可以使用下标 slice [0] ~ slice [4] 来操作里面的元素,capacity 为 10,表示后续向 slice 添加新的元素时可以不必重新分配内存,直接使用预留内存即可
使用数组来创建 Slice 时,Slice 将与原数组共用一部分内存。
例如,语句 slice := array [5:7] 所创建的 Slice,结构如下图所示:
- 切片从数组 array [5] 开始,到数组 array [7] 结束(不含 array [7], 即切片长度为 2, 数组后面的内容都作为切片的预留内存,即 capacity 为 5。数组和切片操作可能作用于同一块内存,这也是使用过程中需要注意的地方。
Slice 如何扩容?
- 使用 append 向 Slice 追加元素时,如果 Slice 空间不足,将会触发 Slice 扩容,扩容实际上重新一配一块更大的内存,将原 Slice 数据拷贝进新 Slice,然后返回新 Slice,扩容后再将数据追加进去。
下图演示了当向一个 capacity 为 5,length 也为 5 的 Slice 再次追加 1 个元素时,就会发生扩容,如下图所示:
- 上图可见:扩容操作只关心容量,会把原 Slice 数据拷贝到新 Slice,追加数据由 append 在扩容结束后完成。扩容后新 Slice 长度仍然是 5,但容量由 5 提升到了 10,原 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。
使用 Copy 操作 Slice 会不会扩容?
- 使用 copy () 内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值。例如长度为 10 的切片拷贝到长度为 5 的切片时,将会拷贝 5 个元素。也就是说,copy 过程中不会发生扩容。
跟据数组或切片生成新的切片是一种比较特殊的切片,这种新生成的切片并没有指定切片的容量,实际上新切片的容量是从 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
使用切片的注意事项:
- 创建切片时可跟据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能;
- 切片拷贝时需要判断实际拷贝的元素个数
- 谨慎使用多个切片操作同一个数组,以防读写冲突
最后总结一下我对切片的理解,如有不足之处讨论区告知:
- 每个切片都指向一个底层数组
- 每个切片都保存了当前切片的长度、底层数组可用容量
- 使用 len () 计算切片长度不需要遍历切片
- 使用 cap () 计算切片容量不需要遍历切片
- 通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体
- 使用 append () 向切片追加元素时有可能触发扩容,扩容后将会生成新的切片
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: