切片, 也可以理解为动态数组. 与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

1. 数据结构
1
2
3
4
5
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
DataLenCapData

golang-slice-struct

2. 初始化

Go 语言中包含三种初始化切片的方式:

2.1 通过下标的方式获得数组或者切片的一部分

1
arr[0:3]

使用下标初始化切片不会拷贝原数组或者原切片中的数据,它只会创建一个指向原数组的切片结构体,所以修改新切片的数据也会修改原切片。

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
arr1 := []int{1, 2, 3, 4, 5}
arr2 := arr1[1:3]
fmt.Println(arr1, arr2)
arr2[0] = 100
fmt.Println(arr1, arr2)
}

/*
[1 2 3 4 5] [2 3]
[1 100 3 4 5] [100 3]
*/

2.2 使用字面量初始化新的切片

1
2
3
4
5
6
7
8
9
10
11
slice := []int{1, 2, 3}

/*
var vstat [3]int
vstat[0] = 1
vstat[1] = 2
vstat[2] = 3
var vauto *[3]int = new([3]int)
*vauto = vstat
slice := vauto[:]
*/
[]int{1, 2, 3}[:]
make
1
slice := make([]int, 10)
runtime.makeslicemake([]int, 3, 4)
1
2
var arr [4]int
n := arr[:3]
runtime.makeslice
1
2
3
4
5
6
7
8
9
10
11
12
func makeslice(et *_type, len, cap int) unsafe.Pointer {
mem, overflow := math.MulUintptr(et.size, uintptr(cap))
if overflow || mem > maxAlloc || len < 0 || len > cap {
mem, overflow := math.MulUintptr(et.size, uintptr(len))
if overflow || mem > maxAlloc || len < 0 {
panicmakeslicelen()
}
panicmakeslicecap()
}

return mallocgc(mem, et, true)
}
3. append

3.1 扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func growslice(et *_type, old slice, cap int) slice {
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}

在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:

  1. 如果期望容量大于当前容量的两倍就会使用期望容量;
  2. 如果当前切片的长度小于 1024 就会将容量翻倍;
  3. 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
runtime.growslice
1
2
3
var arr []int64
arr = append(arr, 1, 2, 3, 4, 5
fmt.Println(len(arr), cap(arr)) //5, 6
runtime.growslicearrsys.PtrSizeruntime.roundupsize

3.2 cap扩容

下面这个例子底层用的同一个数组, cap充足没有重新分配新数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
m := make([]int, 3, 4)
a := append(m, 1)
b := append(m, 2)
fmt.Printf("m: %v p:%p, p:%p,type:%v\n", m, &m, m, reflect.TypeOf(m))
fmt.Printf("a: %v p:%p, p:%p,type:%v\n", a, &a, a, reflect.TypeOf(a))
fmt.Printf("b: %v p:%p, p:%p,type:%v\n", b, &b, b, reflect.TypeOf(b))

a[0] = 10
b[0] = 100
fmt.Printf("m: %v p:%p, p:%p,type:%v\n", m, &m, m, reflect.TypeOf(m))
fmt.Printf("a: %v p:%p, p:%p,type:%v\n", a, &a, a, reflect.TypeOf(a))
fmt.Printf("b: %v p:%p, p:%p,type:%v\n", b, &b, b, reflect.TypeOf(b))
}

/*
m: [0 0 0] p:0xc00007c020, p:0xc00008a000,type:[]int
a: [0 0 0 2] p:0xc00007c040, p:0xc00008a000,type:[]int
b: [0 0 0 2] p:0xc00007c060, p:0xc00008a000,type:[]int

m: [100 0 0] p:0xc00007c020, p:0xc00008a000,type:[]int
a: [100 0 0 2] p:0xc00007c040, p:0xc00008a000,type:[]int
b: [100 0 0 2] p:0xc00007c060, p:0xc00008a000,type:[]int
*/

下面这个例子, 底层用了两个数组, b扩容的时候, cap不足另起炉灶了

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
26
27
func main() {
m := make([]int, 3, 4)
a := append(m, 1)
b := append(a, 2)
fmt.Printf("m: %v p:%p, p:%p,type:%v\n", m, &m, m, reflect.TypeOf(m))
fmt.Printf("a: %v p:%p, p:%p,type:%v\n", a, &a, a, reflect.TypeOf(a))
fmt.Printf("b: %v p:%p, p:%p,type:%v\n", b, &b, b, reflect.TypeOf(b))

a[0] = 10
b[0] = 100
fmt.Printf("m: %v p:%p, p:%p,type:%v\n", m, &m, m, reflect.TypeOf(m))
fmt.Printf("a: %v p:%p, p:%p,type:%v\n", a, &a, a, reflect.TypeOf(a))
fmt.Printf("b: %v p:%p, p:%p,type:%v\n", b, &b, b, reflect.TypeOf(b))

}

/*
m: [0 0 0] p:0xc00000c080, p:0xc000018220,type:[]int
a: [0 0 0 1] p:0xc00000c0a0, p:0xc000018220,type:[]int
b: [0 0 0 1 2] p:0xc00000c0c0, p:0xc00001c180,type:[]int


m: [10 0 0] p:0xc00000c080, p:0xc000018220,type:[]int
a: [10 0 0 1] p:0xc00000c0a0, p:0xc000018220,type:[]int
b: [100 0 0 1 2] p:0xc00000c0c0, p:0xc00001c180,type:[]int

*/

3.3 扩容时底层ptr指向新的地址

  1. 可以看出, cap 是2倍增长的(<1024)
  2. ss 的指针变了
  3. &ss 的指针没有变, &ss操作返回的是该切片的 内部 指针地址,这个“指针地址” 本身地址肯定不变, 只是它指的值变了
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
26
27
28
29
30
31
32
33
34
35
36
func main() {
ss := []int{}
for i := 1; i < 20; i++ {
fmt.Printf("addr:%p %p,len:%d,cap:%d\n", ss, &ss, len(ss), cap(ss))
ss = append(ss, i)
}
}

/*
addr:0x11aac78 0xc0000a6020,len:0,cap:0

addr:0xc0000b4020 0xc0000a6020,len:1,cap:1

addr:0xc0000b4040 0xc0000a6020,len:2,cap:2

addr:0xc0000b6020 0xc0000a6020,len:3,cap:4
addr:0xc0000b6020 0xc0000a6020,len:4,cap:4

addr:0xc0000b8040 0xc0000a6020,len:5,cap:8
addr:0xc0000b8040 0xc0000a6020,len:6,cap:8
addr:0xc0000b8040 0xc0000a6020,len:7,cap:8
addr:0xc0000b8040 0xc0000a6020,len:8,cap:8

addr:0xc0000ba000 0xc0000a6020,len:9,cap:16
addr:0xc0000ba000 0xc0000a6020,len:10,cap:16
addr:0xc0000ba000 0xc0000a6020,len:11,cap:16
addr:0xc0000ba000 0xc0000a6020,len:12,cap:16
addr:0xc0000ba000 0xc0000a6020,len:13,cap:16
addr:0xc0000ba000 0xc0000a6020,len:14,cap:16
addr:0xc0000ba000 0xc0000a6020,len:15,cap:16
addr:0xc0000ba000 0xc0000a6020,len:16,cap:16

addr:0xc0000bc000 0xc0000a6020,len:17,cap:32
addr:0xc0000bc000 0xc0000a6020,len:18,cap:32

*/
4. 实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := slice[2:5]
s2 := s1[2:6:7] // data[low, high, max]

s2 = append(s2, 100)
s2 = append(s2, 200)

s1[2] = 20

fmt.Println(s1)
fmt.Println(s2)
fmt.Println(slice)
}

/*
[2 3 20]
[4 5 6 7 100 200]
[0 1 2 3 20 5 6 7 100 9]
*/

4.1 初始化

slice origin

s1slices2s1

4.2 追加

s2
1
s2 = append(s2, 100)
s2s1

append 100

4.3 扩容

s2
1
s2 = append(s2, 100)
s2s2appends2buffer

append 200

4.4 不同引用

s1
1
s1[2] = 20
s2

s1[2]=20

s1s1
5. 参考资料