Fork me on GitHub

Go之slice陷阱

这个问题隐藏的很深,稍不注意就掉入坑中,并且定位起来耗时费力。

数组(array):
1
var a [...]int{1,2,3}
切片(slice):
1
var b [ ]int{1,2}

注意上述数组和切片的不同之处。

下面通过一段示例代码,我们来仔细讨论下slice的陷阱,稍有不慎,就会掉入坑中。

1
2
3
4
5
6
7
8
9
10
11
12
13
a := []int{0}     /* [0]          */
a = append(a, 0) /* [0, 0] */
b := a[:] /* [0, 0] */
a = append(a, 2) /* [0, 0, 2] */
b = append(b, 1) /* [0, 0, 1] */
fmt.Println("[2]:",a[2]) /* 2 <- 对的 */

// 一样的代码,只是以一个稍大的 slice 开始, c := []int{0, 0} /* [0, 0] */
c = append(c, 0) /* [0, 0, 0] */
d := c[:] /* [0, 0, 0] */
c = append(c, 2) /* [0, 0, 0, 2] */
d = append(d, 1) /* [0, 0, 0, 1] */
fmt.Println("[3]:",c[3]) /* ??? ,正确答案是1 */

输出:

为什么a2的值为2,c3的值为1呢?

原因在与,slice的底层空间,append操作后并非永远是线性增长的,当达到一定的阀值后,slice会重新开辟新的空间(重新申请新的内存)。这样之前本来共享底层空间的两个slice,在其中一个重新开辟新的空间后,就不再共享空间了,也就不再互相影响了。

例如代码中的slice a和b,a在append 2后,就开辟了新的空间,b在append 1操作后也开辟了新的空间,故b的append操作不会影响到a。

但是c和d则不一样,c在进行append 2后,仍然在使用原来的底层空间,d在append 1后也在使用原来的底层空间,故c的操作会被d的append操作覆盖掉。

什么时候slice会开辟新的空间,这是由go的底层算法决定的。没有办法人为控制。

怎么避免这类问题呢?避免slice共享同一块底层空间,尤其是当slice不断增长的情况下。否则你会遇到很多莫名其妙的问题。

您的鼓励是我持之以恒的动力