文章目录
一、Slice介绍
Slice称为动态数组或切片,底层是数组实现,实际使用比数组更加灵活,可以方便地进行扩容和传递。
二、初始化
字面量声明
var s []int // 变量声明
s1 := []int{} // 空切片
s2 := []int{1,2,3}
函数声明
s1 := make([]int,12) // 指定长度
s2 := make([]int,10,20) // 指定长度和容量
s3 := *new([]int) // 空切片
三、特性
1. 切取
切片可以基于其他数组或切片创建,且与原数组和切片共享底层空间,修改切片会影响原数组或切片。
2. 切片表达式
简单表达式
slice := a[low:high]
其中切片长度等于 high - low,容量长度等于底层数组的长度,其中 low 和 high 可以省略。
这种表达式存在两个问题
- 一是在切片使用 appen() 方法追加元素时,大概率会篡改后续元素;
- 二是扩容后的切片底层数组可能不是原数组了。
// 情况一
/* 代码 */
a := [5]int{1, 2, 3, 4, 5}
aSlice := a[1:2] // 前闭后开,取下标为1的位置,即 2 = a[1]
fmt.Println("aSlice:", aSlice, " aSliceLen:", len(aSlice), " aSliceCap:", cap(aSlice)) // aSlice: [2] aSliceLen: 1 aSliceCap: 4
fmt.Println("a:", a) // a: [1 2 3 4 5]
aSlice = append(aSlice, 8) // 追加一个新元素
fmt.Println("aSlice:", aSlice, " aSliceLen:", len(aSlice), " aSliceCap:", cap(aSlice)) // aSlice: [2 8] aSliceLen: 2 aSliceCap: 4
fmt.Println("a:", a) // a: [1 2 8 4 5]
/* 控制台 */
aSlice: [2] aSliceLen: 1 aSliceCap: 4
a: [1 2 3 4 5]
aSlice: [2 8] aSliceLen: 2 aSliceCap: 4
a: [1 2 8 4 5] // a[2]位置已经从 3 -> 8
// 情况二
/* 代码 */
a := [5]int{1, 2, 3, 4, 5}
fmt.Println("a:", a)
aSlice := a[0:]
aSlice[0] = 2 // 修改下标0位置元素
fmt.Println("aSlice:", aSlice, " a:", a)
aSlice = append(aSlice, 9) // 简单表达式下 cap 到达数组极限,扩容后的切片将不再影响原数组
aSlice[0] = 5 // 修改下标0位置元素
fmt.Println("aSlice:", aSlice, " a:", a)
/* 控制台 */
原数组 a: [1 2 3 4 5]
aSlice: [2 2 3 4 5] a: [2 2 3 4 5] // 修改下标0位置后的 aSlice 和 a,此时切片影响了原数组
aSlice: [5 2 3 4 5 9] a: [2 2 3 4 5] // 触发扩容后的 aSlice 和数组,此时的修改操作对原数组没有影响,说明扩容产生的新切片底层数组已经不是原数组。
*边界问题:0 ≤ low ≤ high ≤ cap(a),否则触发 panic | 意味着 high 的取值可以大于 len(a) *
扩展表达式
slice := a[low:high:max]
限制新切片容量的表达式,因为简单表达式可能会覆盖 a[high] 后的元素,产生意料之外的影响。
边界问题:0 ≤ low ≤ high ≤ max ≤ cap(a)
3. 其他方法
appen()
向切片追加元素。
当切片 len=cap 时,向切片追加元素会产生扩容,扩容后生成的新切片将不会影响原数组或切片。
扩容: 当切片容量不足时会重新申请内存空间更大的新切片,将原切片内容复制到新切片,最后追加元素。
扩容规则: ① Slice容量<1024时,申请两倍内存空间;② Slice容量≥1024时,申请1.25倍内存空间。
len()
查询切片长度
cap()
查询切片容量
二、原理
数据结构
// src/runtime/slice.go:slice
type slice struct {
array unsafe.Pointer // 底层数组指针
len int // 切片长度
cap int // 切片容量
}
- 底层数组指针指向数组中某个节点,例如[1,2,3],它可以指向 2
- 切片容量 cap 映射底层数组的大小
- 切片长度 len 反映切片访问的范围,切片访问下标大于 len 时会触发 panic
小结
- 每个切片都只想一个底层数组,每个切片都保存了当前切片的长度和容量
- 使用 len()、cap() 方法计算切片长度和容量的时间复杂度为O(1)
- 通过函数传递切片不会拷贝整个切片,切片本身只是一个数据结构
- 使用 appen() 方法向切片追加元素可能触发扩容,扩容会生成新切片
Tip
- 根据实际需要分配容量,避免在追加过程中扩容影响性能
- 谨慎使用多个切片操作同一数组/切片,防止冲突
- 使用扩展表达式代替简单表达式
Question
当切片很小,但底层数组占据了大量空余内存,该如何优化?