Go什么时候分配新的支持数组来切片?

问题描述 投票:1回答:2

当阅读Go切片时,我在append方法的上下文中遇到了这种行为

如果s的后备数组太小而无法满足所有给定值,则将分配更大的数组。返回的切片将指向新分配的数组。 来源 - Golang Tour

为了理解这一点,我编写了以下代码:

Try on the Go Playground

func makeSlices() {
    var a []int;

    a = append(a, 0)

    b := append(a, 1)
    printSlice("b", b)

    c := append(a, 2)
    printSlice("b", b)
    printSlice("c", c)

}

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

输出:

var=b len=2 cap=2 first_address=0x414020 [0 1]
var=b len=2 cap=2 first_address=0x414020 [0 2]
var=c len=2 cap=2 first_address=0x414020 [0 2]

我希望bc指向相同的底层数组,因为它们都是相同长度的切片


但是,如果我要为另一段切片改变相同的代码:

Try on the Go Playground

func makeSlices() {
    var a []int;

    a = append(a, 0, 9)

    d := append(a, 1, 2)
    printSlice("d", d)

    e := append(a, 3, 4)
    printSlice("d", d)
    printSlice("e", e)
}

输出:

var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=d len=5 cap=8 first_address=0x450020 [0 0 9 1 2]
var=e len=5 cap=8 first_address=0x450040 [0 0 9 3 4]

在这种情况下,de应指向相同的后备阵列,因为它们是相同长度的切片,但它们不会。


为什么这种行为异常? Go什么时候决定将新的支持数组分配给切片?

go slice
2个回答
4
投票

答案很简单:如果要追加的元素不适合当前容量,append()会分配一个新的后备阵列(并复制当前内容)。形式上:

if len(s) + len(newElements) > cap(s) {
    // Allocate new backing array
    // copy content (s) over to new array
} else {
    // Just resize existing slice
}
// append (copy) newElements

因此,例如,如果len = 2,cap = 4,您可以附加2个元素,不分配。

如果len = 2,cap = 4,并且你追加3个元素,那么len + 3> cap,那么将分配一个新的支持数组(其容量将大于len + 3,考虑未来增长,但其长度将为是2 + 3 = 5)。

解释你的第一个例子

在第一个示例中,您声明了一个具有0长度和容量的切片变量。

var a []int
fmt.Println(len(a), cap(a)) // Prints 0 0

当您执行第一次追加时,将分配一个新数组:

a = append(a, 0)
fmt.Println(len(a), cap(a)) // Prints 1 2

当你做另一个追加时,它适合容量,所以没有分配:

fmt.Println(len(a), cap(a)) // Prints 1 2
b := append(a, 1)
fmt.Println(len(b), cap(b)) // Prints 2 2

但是这次你将结果切片存储在b中,而不是存储在a中。因此,如果你对a做第3次附加,那么仍然有length = 1和cap = 2,所以将另一个元素附加到a将不需要分配:

fmt.Println(len(a), cap(a)) // Prints 1 2
c := append(a, 2)
fmt.Println(len(c), cap(c)) // Prints 2 2

因此,排除第一个附加,所有其他附加不需要分配,因此第一个分配的后备数组用于所有abc切片,因此它们的第一个元素的地址将是相同的。这就是你所看到的。

解释你的第二个例子

再次创建一个空切片(len = 0,cap = 0)。

然后你做第一个追加:2个元素:

a = append(a, 0, 9)
fmt.Println(len(a), cap(a)) // Prints 2 2

这会分配一个长度为2的新数组,因此切片的长度和容量都将为2。

然后你做第二次追加:

d := append(a, 1, 2)
fmt.Println(len(d), cap(d)) // Prints 4 4

由于没有更多元素的空间,因此分配了一个新数组。但是你在d中存储指向这个新数组的切片,而不是在a中。 a仍指向旧阵列。

然后你做第3次追加,但是a(指向旧数组):

fmt.Println(len(a), cap(a)) // Prints 2 2
e := append(a, 3, 4)
fmt.Println(len(e), cap(e)) // Prints 4 4

同样,a数组不能容纳更多元素,因此会分配一个新数组,存储在e中。

所以de有不同的支持数组,并且附加到任何与“另一个”切片共享支持数组的切片不会(不能)改变这个“另一个”切片。因此,结果是您看到d的相同地址两次,以及e的不同地址。


1
投票

我在你的例子中添加了另外几行,看看here

看看第一个printSlice("a", a)。长度为1,容量为2.当您添加项目时,不需要分配更大的底层数组,因此bc使用相同的数组。

一旦长度超过2(d := append(c, 3)),就会为d分配一个新的后备阵列。 c保持不变。因此,当创建e时,会创建另一个新的后备阵列。

© www.soinside.com 2019 - 2024. All rights reserved.