go slice 特性
1、slice 并不是数组或数组指针。它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。
- 引⽤类型。但⾃⾝是结构体,值拷⻉传递。
- 属性 len 表⽰可⽤元素数量,读写操作不能超过该限制。
- 属性 cap 表⽰最⼤扩张容量,不能超出数组限制。
- 如果 slice == nil,那么 len、cap 结果都等于 0。
runtime.h
struct Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};
len = high - low
cap = max - low
示例:
func test1() {
data := [...]int {0,1,2,3,4,5,6,7,8,9}
s1 := data[:6:8] // 省略low
s2 := data[5:] // 省略high、max
s3 := data[:3] // 省略low、max
s4 := data[:] // 省略low、high、max,相当于引用整个数组
fmt.Printf("the value of s1: %v, cap: %d, len: %d\n", s1, cap(s1), len(s1))
fmt.Printf("the value of s2: %v, cap: %d, len: %d\n", s2, cap(s2), len(s2))
fmt.Printf("the value of s3: %v, cap: %d, len: %d\n", s3, cap(s3), len(s3))
fmt.Printf("the value of s4: %v, cap: %d, len: %d\n", s4, cap(s4), len(s4))
s1[1] = 100
fmt.Printf("the value of s1: %v, cap: %d, len: %d\n", s1, cap(s1), len(s1))
fmt.Printf("the value of s4: %v, cap: %d, len: %d\n", s4, cap(s4), len(s4))
}
输出:
the value of s1: [0 1 2 3 4 5], cap: 8, len: 6
the value of s2: [5 6 7 8 9], cap: 5, len: 5
the value of s3: [0 1 2], cap: 10, len: 3
the value of s4: [0 1 2 3 4 5 6 7 8 9], cap: 10, len: 10
the value of s1: [0 100 2 3 4 5], cap: 8, len: 6
the value of s4: [0 100 2 3 4 5 6 7 8 9], cap: 10, len: 10
2、可直接创建 slice 对象,⾃动分配底层数组。
示例:
func test2() {
s1 := []int {0, 1, 2, 3, 8:100} // 通过初始化表达式构造,可使⽤索引号。与构造数组的区别就是没有指定长度。
s2 := make([]int, 6, 8) // 使⽤ make 创建,指定 len 和 cap 值。
s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
fmt.Printf("the value of s1: %v, cap: %d, len: %d\n", s1, cap(s1), len(s1))
fmt.Printf("the value of s2: %v, cap: %d, len: %d\n", s2, cap(s2), len(s2))
fmt.Printf("the value of s3: %v, cap: %d, len: %d\n", s3, cap(s3), len(s3))
p := &s1[2] // 可通过指针直接操作底层数组
*p += 100
fmt.Printf("the value of s1: %v, cap: %d, len: %d\n", s1, cap(s1), len(s1))
}
输出:
the value of s1: [0 1 2 3 0 0 0 0 100], cap: 9, len: 9
the value of s2: [0 0 0 0 0 0], cap: 8, len: 6
the value of s3: [0 0 0 0 0 0], cap: 6, len: 6
the value of s1: [0 1 102 3 0 0 0 0 100], cap: 9, len: 9
使⽤ make 动态创建 slice,避免了数组必须⽤常量做⻓度的⿇烦。
3、切片的切片。
⾄于[][]T
,是指元素类型为 []T
。
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
4、reslice
所谓 reslice,是基于已有 slice 创建新 slice 对象,以便在 cap 允许范围内调整属性。
示例:
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[2:5] // [2 3 4]
s2 := s1[2:6:7] // [4 5 6 7]
s3 := s2[3:6] // Error
输出:
+---+---+---+---+---+---+---+---+---+---+
data | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---+---+---+---+---+---+---+---+---+---+
0 2 5
+---+---+---+---+---+---+---+---+
s1 | 2 | 3 | 4 | | | | | | len = 3, cap = 8
+---+---+---+---+---+---+---+---+
0 2 6 7
+---+---+---+---+---+
s2 | 4 | 5 | 6 | 7 | | len = 4, cap = 5
+---+---+---+---+---+
0 3 4 5
+---+---+---+
s3 | 7 | 8 | X | error: slice bounds out of range
+---+---+---+
新对象依旧指向原底层数组:
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := s[2:5] // [2 3 4]
s1[2] = 100
s2 := s1[2:6] // [100 5 6 7]
s2[3] = 200
fmt.Println(s) // [0 1 2 3 100 5 6 200 8 9]
5、append
向 slice 尾部添加数据,返回新的 slice 对象。
示例:
func test4() {
s := make([]int, 1, 5)
s1 := append(s, 100)
s2 := append(s1, 200, 300)
fmt.Printf("the value of s: %v, the address of s: %p\n", s, &s)
fmt.Printf("the value of s1: %v, the address of s1: %p\n", s1, &s1)
fmt.Printf("the value of s2: %v, the address of s2: %p\n", s2, &s2)
fmt.Printf("the original array is: %v\n", *(*[5]int)(unsafe.Pointer(&s[0]))) // 为了打印原始数组,使用强制类型转换
}
输出:
the value of s: [0], the address of s: 0xc000004480
the value of s1: [0 100], the address of s1: 0xc0000044a0
the value of s2: [0 100 200 300], the address of s2: 0xc0000044c0
the original array is: [0 100 200 300 0]
简单点说,就是在 array[slice.high] 写数据。
切片后添加另一个切片:
示例:
func test8() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5, 6}
s3 := append(s1, s2...)
s4 := append(s1[:1], s2...) // 将s2添加到s1[0]末尾
s5 := append(s1, s2[:2]...) // 将s2[0]、s2[1]添加到s1末尾
fmt.Printf("the value of s3: %v, the len: %d, the cap: %d\n", s3, len(s3), cap(s3))
fmt.Printf("the value of s4: %v, the len: %d, the cap: %d\n", s4, len(s3), cap(s4))
fmt.Printf("the value of s5: %v, the len: %d, the cap: %d\n", s5, len(s5), cap(s5))
}
输出:
the value of s3: [1 2 3 4 5 6], the len: 6
the value of s4: [1 4 5 6], the len: 4
the value of s5: [1 2 3 4 5], the len: 5
⼀旦超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。
示例:
func test5() {
data := [...]int {0,1,2,3,4,5,6,7,8,9}
s1 := data[:2:3]
s2 := append(s1,100,200)
fmt.Printf("the value of s1: %v, the address of s1[0]: %p\n", s1, &s1[0])
fmt.Printf("the value of s2: %v, the address of s2[0]: %p\n", s2, &s2[0])
}
输出:
the value of s1: [0 1], the address of s1[0]: 0xc00000e140
the value of s2: [0 1 100 200], the address of s2[0]: 0xc00000a330
从输出结果可以看出,append 后的 s2 重新分配了底层数组,并复制数据。如果只追加⼀个值,则不会超过 s1.cap 限制,也就不会重新分配。
通常以 2 倍容量重新分配底层数组。在⼤批量添加数据时,建议⼀次性分配⾜够⼤的空间,以减少内存分配和数据复制开销。或初始化⾜够⻓的 len 属性,改⽤索引号进⾏操作。及时释放不再使⽤的 slice 对象,避免持有过期数组,造成 GC ⽆法回收。
查看cap增长趋势:
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
输出:
cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64
6、copy
函数 copy 在两个 slice 间复制数据,复制⻓度以 len ⼩的为准。两个 slice 可指向同⼀底层数组,允许元素区间重叠。
示例:
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := data[8:]
s2 := data[:5]
copy(s2, s) // dst:s2, src:s
fmt.Println(s2)
fmt.Println(data)
输出:
[8 9 2 3 4]
[8 9 2 3 4 5 6 7 8 9]
copy(dst, src)
及时将所需数据 copy 到较⼩的 slice,以便释放超⼤号底层数组内存。