为了账号安全,请及时绑定邮箱和手机立即绑定

Go什么时候分配一个新的后备数组给slice?

Go什么时候分配一个新的后备数组给slice?

Go
手掌心 2023-06-12 16:52:24
在阅读 Go 切片时,我在方法的上下文中遇到了这种append行为如果 s 的后备数组太小而无法容纳所有给定值,则会分配一个更大的数组。返回的切片将指向新分配的数组。为了理解这一点,我编写了以下代码:在 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]我希望b并c指向相同的底层数组,因为它们都是相同长度的切片但是如果我要为另一个长度的切片改变相同的代码:在 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]在这种情况下,d和e应该指向相同的后备数组,因为它们又是相同长度的切片,但它们不是。为什么会出现这种行为异常?Go 到底什么时候决定为切片分配一个新的支持数组?
查看完整描述

2 回答

?
qq_花开花谢_0

TA贡献1835条经验 获得超6个赞

答案很简单: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,那么会分配一个新的backing array(容量会大于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. 因此,如果您对 进行第 3 次追加a,它仍然具有 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

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


解释你的第二个例子

您再次创建一个空切片(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仍然指向旧数组。


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


fmt.Println(len(a), cap(a)) // Prints 2 2

e := append(a, 3, 4)

fmt.Println(len(e), cap(e)) // Prints 4 4

同样,array ofa不能容纳更多元素,因此分配了一个新数组,您将其存储在e.


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


查看完整回答
反对 回复 2023-06-12
?
慕沐林林

TA贡献2016条经验 获得超9个赞

看第一个printSlice("a", a)。长度为 1,容量为 2。当你添加一个项目时,不需要分配一个更大的底层数组,所以同一个数组用于bc

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


查看完整回答
反对 回复 2023-06-12
  • 2 回答
  • 0 关注
  • 82 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信