【注意】最后更新于 November 19, 2018,文中内容可能已过时,请谨慎使用。
今天在看《go in action》的时候,看到切片这一章对于切片分配有点不太理解,做了几个实验理解了,感觉很有意义,记录下来。
首先我们要说一点,在golang中大部分都是值拷贝,但是slice并不是,而是基于长度进行的选择。
我们先做最简单的例子
1
2
3
4
5
6
7
|
func TestAppend(t *testing.T) {
s1 := []int{1, 2, 3, 4, 5}
s2 := s1[0:3]
s2 = append(s2, 30)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
|
可以看到输出结果是
1
2
|
[1 2 3 30 5] 5 5
[2 3 30] 3 4 |
很明显,对s2处理之后也修改了s1中的值,但是由于s2的容量相当于从0开始占据了s1的容量,所以总容量是4
我们可以很大胆地猜测,如果我们直接截取S1后往里面添加两个值,那么他自身的后两个值一定会被覆盖,我们做个实验:
1
2
3
4
5
6
7
|
func TestAppend(t *testing.T) {
s1 := []int{1, 2, 3, 4, 5}
s2 := s1[1:3]
s2 = append(s2, 30, 40)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
|
输出结果是:
1
2
|
[1 2 3 30 40] 5 5
[2 3 30 40] 4 4 |
和我们猜测的结果是一致的。如果我们添加的数据超出了s1的容量呢?
1
2
3
4
5
6
7
|
func TestAppend(t *testing.T) {
s1 := []int{1, 2, 3, 4, 5}
s2 := s1[1:3]
s2 = append(s2, 30, 40, 50)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
|
程序的输出结果是
1
2
|
[1 2 3 4 5] 5 5
[2 3 30 40 50] 5 8 |
可以发现,程序没有覆盖原来s1的数据,而是直接开辟了一段新的空间,将原来的值赋值进去,创建了大小为8的空间。为什么会是8呢?
我们不妨做个实验:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func TestAppend2(t *testing.T) {
var tmp []int
s2 := []int{1, 2, 3, 4}
fmt.Print(cap(s2), ",")
tmp = make([]int, 2)
s2 = append(s2, tmp...)
fmt.Print(cap(s2), ",")
tmp = make([]int, 4)
s2 = append(s2, tmp...)
fmt.Print(cap(s2), ",")
tmp = make([]int, 6)
s2 = append(s2, tmp...)
fmt.Print(cap(s2), ",")
tmp = make([]int, 7)
s2 = append(s2, tmp...)
fmt.Println(cap(s2), ",")
}
|
大家可以猜测一下程序的输出结果
发现没有,每次添加数据之后,都是相对本slice的容量进行两倍放大,所以可以理解成一个慢性增的过程吧!
重新回到slice值传递,如果我们每次增加一个值,那么会覆盖吗?
我们再做个实验:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func TestAppend(t *testing.T) {
s1 := []int{1, 2, 3, 4, 5}
s2 := s1[1:3]
s2 = append(s2, 30)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
s2 = append(s2, 40)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
s2 = append(s2, 50)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
|
输出结果如下
1
2
3
4
5
6
|
[1 2 3 30 5] 5 5
[2 3 30] 3 4
[1 2 3 30 40] 5 5
[2 3 30 40] 4 4
[1 2 3 30 40] 5 5
[2 3 30 40 50] 5 8 |
也就是逐步覆盖的,直到超出容量为本slice分配新的空间
所以我们可以得到结论了,初始从slice进行截取操作的时候,是类似于指针传递,指向了原来的数组下标的位置,一旦超出本slice的容量,就会相对本slice大小开辟双倍的slice大小,进行存储。
我们在以后程序涉及的时候,需要考虑到一个问题了,如果希望对slice进行值拷贝,就不要直接使用截取操作了,而是使用append或者copy进行值传递才能保证数据安全。