Slice

切片

  1. 切片与数组区别
  2. 切片在函数之间传递是值传递还是引用传递
  3. append方法是如何进行拷贝的,会产生双倍空间吗
  4. append函数与copy函数不同
  5. 子切片修改,会影响父切片的数据
  6. 存在的陷阱
  7. Append扩容隐藏的坑
  • 当把slice传给一个函数时,对slice的结构体发生的值传递,而slice中指向数据内容的地址没有变
  • 切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素
  • 切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据,导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里,只能通过切片拷贝修复
  • 在使用append()函数给slice中添加元素时,slice的初始大小可以为0,也就是len可以为0。每次向slice中append的时候,如果容量cap不够,会自动对slice进行扩容,也就是改变slice的cap的大小。
  • 而在使用copy()函数操作slice时,如果slice的大小为0时,会不添加任何元素,不会自动增加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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
"log"
"testing"
"unsafe"
)

func init() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
}

func sliceTest(list []int) {
if len(list) > 0 {
log.Println("func pos 0:", &list[0])
}

log.Println("func pointer:", unsafe.Pointer(&list))
log.Println("------")
}

func copyTest() {
list := make([]int, 0)
log.Println("pointer:", unsafe.Pointer(&list))
sliceTest(list)
log.Println("++++++++++++++")

list = append(list, 1)
log.Println("pos 0:", &list[0])
log.Println("pointer:", unsafe.Pointer(&list))
sliceTest(list)
log.Println("++++++++++++++")

list = append(list, 1, 3, 4, 5)
log.Println("pos 0:", &list[0])
log.Println("pointer:", unsafe.Pointer(&list))
list2 := list
log.Println("pointer:", unsafe.Pointer(&list2))
log.Println("pos 0:", &list2[0])
list3 := list[3:]
log.Println("pos 1:", &list[0])
log.Println("pos 1:", &list[1])
log.Println("pos 1:", &list[2])
log.Println("pos 1:", &list[3])
log.Println("pos 1:", &list[4])
log.Println("pointer:", unsafe.Pointer(&list3))
log.Println(list3)
log.Println(list)
log.Println("pos 0:", &list3[0])
log.Println("pos 0:", &list3[1])
log.Println("cap",cap(list3))
log.Println("cap",len(list3))
list3[0] = 8
log.Println(list)
log.Println(list3)
list3 = nil
log.Println(list)
log.Println(list3)
log.Println("++++++++++++++")

list = append(list, 2)
log.Println("pos 0:", &list[0])
log.Println("pointer:", unsafe.Pointer(&list))
sliceTest(list)
log.Println("++++++++++++++")

copy1 := make([]int, 0)
copy(copy1, list)
log.Println("copy1:", copy1)

copy2 := make([]int, 2)
copy(copy2, list)
log.Println("copy2:", copy2)

copy3 := make([]int, 0, 2)
copy(copy3, list)
log.Println("copy3:", copy3)
log.Println()
}

func Test(t *testing.T) {
copyTest()
}

append vs copy

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package append_vs_copy

import (
"testing"
)

func TestCopy(t *testing.T) {
y := doCopy(true, false)
if len(y) != 1000 {
t.Fatalf("Expected len(y) to be 1000 but was %d", len(y))
}
}

func TestAppend(t *testing.T) {
y := doCopy(false, false)
if len(y) != 1000 {
t.Fatalf("Expected len(y) to be 1000 but was %d", len(y))
}
}

func TestAppendAlloc(t *testing.T) {
y := doCopy(false, true)
if len(y) != 1000 {
t.Fatalf("Expected len(y) to be 1000 but was %d", len(y))
}
}

func doCopy(useCopy bool, preAlloc bool) []int64 {
existing := make([]int64, 1000, 1000)
var y []int64
if useCopy {
y = make([]int64, 1000, 1000)
copy(y, existing)
} else {
var init []int64

if preAlloc {
init = make([]int64, 0, 1000)
} else {
init = []int64{}
}
y = append(init, existing...)
}
return y
}

func BenchmarkAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
doCopy(false, false)
}
}

func BenchmarkAppendAlloc(b *testing.B) {
for i := 0; i < b.N; i++ {
doCopy(false, true)
}
}

func BenchmarkAppendAllocInline(b *testing.B) {
for i := 0; i < b.N; i++ {
existing := make([]int64, 1000, 1000)
var init []int64

init = make([]int64, 0, 1000)
_ = append(init, existing...)
}
}

func BenchmarkCopy(b *testing.B) {
for i := 0; i < b.N; i++ {
doCopy(true, true)
}
}

Go 切片:用法和本质
golang中append函数和copy函数操作slice的不同用法
如何避开 Go 中的各种陷阱