原理

go语言slice是底层数组的一个视图。
slice主要参数有3个:

  1. ptr指向slice的首个元素;
  2. len表示slice长度;
  3. cap表示slice容量。

当向sliceappend元素的时候,如果元素个数大于容量的某个百分比,将会扩容。其ptr值也会变。
slice扩容的原理其实就是更换该slice底层array数组(因为slice只是某一数组的视图)

定义时与数组的区别

arrayslice的定义

1
2
3
4
5
6
7
8
9
10
// array定义
array1 := [3]int{1,2,3}
array2 := [...]int{1,2,3}

// slice定义
slice1 := []int{1,2,3}
slice2 := make([]int,0,10)
slice3 := array1[:] // array1 := [3]int{1,2,3}
slice4 := slice1[:]
var slice5 = []int

测试

测试1

测试slicearray定义区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import (
"fmt"
"reflect"
)
func main() {

array1 := [3]int{1,2,3}
array2 := [...]int{1,2,3}

slice1 := []int{1,2,3}
slice2 := make([]int,5,10)
slice3 := array1[:]
slice4 := slice1[:]
var slice5 []int

fmt.Printf("array1 type : %v\n",reflect.TypeOf(array1).Kind())
fmt.Printf("array2 type : %v\n",reflect.TypeOf(array2).Kind())
fmt.Println("===================")
fmt.Printf("slice1 type : %v\n",reflect.TypeOf(slice1).Kind())
fmt.Printf("slice2 type : %v\n",reflect.TypeOf(slice2).Kind())
fmt.Printf("slice3 type : %v\n",reflect.TypeOf(slice3).Kind())
fmt.Printf("slice4 type : %v\n",reflect.TypeOf(slice4).Kind())
fmt.Printf("slice5 type : %v\n",reflect.TypeOf(slice5).Kind())
}

输出结果:

1
2
3
4
5
6
7
8
array1 type : array
array2 type : array
===================
slice1 type : slice
slice2 type : slice
slice3 type : slice
slice4 type : slice
slice5 type : slice

测试2

测试slice扩容条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
func main() {

tmp := [...]int{0, 1, 2, 3}
arr := tmp[1:]
Cap := cap(arr)
lastCap := Cap
for i := 0; i < 20000; i++ {
arr = append(arr, 0)
Cap = cap(arr)
if Cap == lastCap {
continue
}
fmt.Printf("%p\tlen:%v\t\tlastCap:%v\t\tcap:%v\n", arr, len(arr), lastCap, Cap)
lastCap = Cap
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0xc000072030	len:4		lastCap:3		cap:6
0xc00003e060 len:7 lastCap:6 cap:12
0xc00008a000 len:13 lastCap:12 cap:24
0xc00008c000 len:25 lastCap:24 cap:48
0xc00008e000 len:49 lastCap:48 cap:96
0xc000090000 len:97 lastCap:96 cap:192
0xc000092000 len:193 lastCap:192 cap:384
0xc000098000 len:385 lastCap:384 cap:768
0xc00009e000 len:769 lastCap:768 cap:1536
0xc0000a4000 len:1537 lastCap:1536 cap:2048
0xc0000a8000 len:2049 lastCap:2048 cap:2560
0xc0000b2000 len:2561 lastCap:2560 cap:3408
0xc0000c6000 len:3409 lastCap:3408 cap:5120
0xc0000d0000 len:5121 lastCap:5120 cap:7168
0xc0000de000 len:7169 lastCap:7168 cap:9216
0xc0000f0000 len:9217 lastCap:9216 cap:12288
0xc000108000 len:12289 lastCap:12288 cap:15360
0xc000126000 len:15361 lastCap:15360 cap:19456
0xc00014c000 len:19457 lastCap:19456 cap:24576

结论:

  1. 当扩容时,ptr指针变(即slice这个视图所在的array发生变化)
  2. 只有当cap大小等于len的时候才会扩容,扩容大小视cap大小而定:cap较小时,直接扩容一倍,稍大时,扩容比例较小

测试3

测试扩容前后的slice与原slice有什么不同以及两个相同的slice执行某些操作后,另一个的变化情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main
import "fmt"
func main() {

arr := make([]int,2,16)
arr2 := arr
// 扩容前两者地址一样,修改一个,另一个值也修改
arr[0] = 5
fmt.Printf("arr\t%p\t[0] : %v\tlen : %v\tcap : %v\n", arr, arr[0], len(arr), cap(arr))
fmt.Printf("ar2\t%p\t[0] : %v\tlen : %v\tcap : %v\n\n", arr2, arr2[0], len(arr2), cap(arr2))
// 扩容前,当一个增加元素时,另一个slice长度不变
arr = append(arr, 0)
fmt.Printf("arr\t%p\t[0] : %v\tlen : %v\tcap : %v\n", arr, arr[0], len(arr), cap(arr))
fmt.Printf("ar2\t%p\t[0] : %v\tlen : %v\tcap : %v\n\n", arr2, arr2[0], len(arr2), cap(arr2))
// 循环,令arr扩容
for i := 0; i < 100; i++ {
arr = append(arr, 0)
}
// 扩容后,arr变成一个新的slice,修改其中一个slice的值,另一个不会变
fmt.Printf("arr\tptr : %p\tlen : %v\tcap : %v\n", arr, len(arr), cap(arr))
fmt.Printf("ar2\tptr : %p\tlen : %v\tcap : %v\n\n", arr2, len(arr2), cap(arr2))
arr[0] = 100
fmt.Printf("arr\t[0] : %v\n", arr[0])
fmt.Printf("ar2\t[0] : %v\n", arr2[0])
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
arr	0xc00007e080	[0] : 5	len : 2	cap : 16
ar2 0xc00007e080 [0] : 5 len : 2 cap : 16

arr 0xc00007e080 [0] : 5 len : 3 cap : 16
ar2 0xc00007e080 [0] : 5 len : 2 cap : 16

arr ptr : 0xc00008c000 len : 103 cap : 128
ar2 ptr : 0xc00007e080 len : 2 cap : 16

arr [0] : 100
ar2 [0] : 5

结论

  1. 未发生扩容,两个slice相同,改变其中一个slice,另一个也变
  2. 未发生扩容,向其中一个增加元素,另一个len不变
  3. 发生扩容,新生成slice,地址变,改变其中一个slice另一个不变

测试4

测试对于两个引用同一个数组但len不同的slice,能否取到超出自身len的元素

1
2
3
4
5
6
7
8
9
10
11
package main
import "fmt"
func main() {

tmp := [...]int{0,1,2,3,4,5,6,7}
s1 := tmp[2:6] // s1 : [2,3,4,5]
fmt.Println(len(s1)) // s1 长度只有1
s2 := s1[3:6] // s2 : [5,6,7]
// fmt.Println(s2[3]) // 报错
fmt.Println(s2)
}

输出

1
2
4
[5 6 7]

结论:

  1. 如实例,s1长度为1,使用[:]操作,能取到后边的数字
  2. 如实例,s1长度为1,无法使用s1[1]取到对应位置的数据