为什么Golang的切片操作如此复杂

时间:2016-11-22 22:28:07

标签: go

这是我在Golang的第一天,当我尝试切片操作时,就像append()一样,有一件事让我很困惑:

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    a:= s[2:4];
    a = append(a, 1000, 1001);
    a[1] = 100; 
    printSlice("a:", a)
    printSlice("s:", s)
}

func printSlice(title string, s []int) {
    fmt.Printf("%s  len=%d cap=%d %v\n", title,  len(s), cap(s), s)
}

当我只向a添加两个数字时,例如:

a = append(a, 1000, 1001);

......结果是:

a:  len=4 cap=4 [5 100 1000 1001]
s:  len=6 cap=6 [2 3 5 100 1000 1001]

我认为,a显示s作为对a = append(a, 1000, 1001, 1002); 的引用。

但是当我将这行代码更改为:

a:  len=5 cap=8 [5 100 1000 1001 1002]
s:  len=6 cap=6 [2 3 5 7 11 13]

......结果变为:

a

我认为,s已被重新分配给另一段内存,以保存整个内容,并将引用分离为slice

这是如此不一致,让我如此困惑,有时很容易犯这个错误(就像你有一个随机数量的值要追加)。

为什么Golang这样设计?如果我只想要JavaScript pushpackage.json之类的操作,怎么可以避免这种情况?

1 个答案:

答案 0 :(得分:5)

这与在Go中实现切片的方式有关。

slice's struct看起来像:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

因此,切片具有长度和容量。如果您尝试将项目附加到切片以使其超过当前容量,则会在下面创建一个新数组以保存新数据,但由于之前的子数据仍可能指向较旧的数组,因此它将保持原样直到有没有更多的参考资料。

现在假设我们有一个切片A[1, 2, 3, 4, 5, 6]和一个子切片B,它指向A中的最后3项:[4,5,6]。

[1, 2, 3, 4, 5, 6]
 ^        ^
 |        |
 |        B------
 |
 A---------------  

现在,如果我们将一个项目追加到B然后根据您的预期行为,它应该更新A,因此会创建一个新数组。如果子片段的大小与实际数组相比较小(用于附加从原始数组中复制1000个项目的1个项目),则效率低下。

另外为了保持一致,指向旧数组的所有其他引用(子句)必须更新以指向新数组中的适当位置,这意味着我们必须在切片中存储其他信息,如启动指数。如果我们有一个子片的子片,这可能会变得棘手。

因此,目前的实施是有道理的。

这里推荐的方法是复制子片而不是直接处理它以防止此类问题。拥有副本的另一个好处是,如果原始切片很大并且不再有引用,那么它可以被垃圾收集,但是如果它有一个子切片,那么原始数组将保持它的内存,直到子切片仍然引用它。