Golang:附加切片或 w/o 分配

Golang: appending slices with or w/o allocation

Go 的 append() 函数仅在给定切片的容量不足时分配新的切片数据(另请参阅:)。这可能会导致意外行为(至少对我这个 golang 新手来说):

package main

import (
    "fmt"
)

func main() {

    a1 := make([][]int, 3)
    a2 := make([][]int, 3)
    b := [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}
    common1 := make([]int, 0)
    common2 := make([]int, 0, 12) // provide sufficient capacity
    common1 = append(common1, []int{10, 20}...)
    common2 = append(common2, []int{10, 20}...)

    idx := 0
    for _, k := range b {
        a1[idx] = append(common1, k...) // new slice is allocated
        a2[idx] = append(common2, k...) // no allocation
        idx++
    }

    fmt.Println(a1)
    fmt.Println(a2) // surprise!!!
}

输出:

[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]

[[10 20 3 3 3] [10 20 3 3 3] [10 20 3 3 3]]

https://play.golang.org/p/8PEqFxAsMt

那么,Go 中强制分配新切片数据或更准确地说确保 append() 的切片参数保持不变的(惯用)方法是什么?

您可能对切片在 Go 中的工作方式有错误的认识。

当您将元素追加到切片时,调用 append() returns 一个新切片。如果没有发生重新分配,两个切片值——你调用 append() 的那个和它返回的那个——共享同一个支持数组 但是 它们将有不同的长度;观察:

package main

import "fmt"

func main() {
    a := make([]int, 0, 10)
    b := append(a, 1, 2, 3)
    c := append(a, 4, 3, 2)
    fmt.Printf("a=%#v\nb=%#v\nc=%#v\n", a, b, c)
}

输出:

a=[]int{}
b=[]int{4, 3, 2}
c=[]int{4, 3, 2}

因此,len(a) == 0len(b) == 3len(c) == 3 和对 append() 的第二次调用 owerwrote 第一次调用所做的,因为所有切片共享相同的底层数组.

关于后备数组的重新分配,the spec很清楚:

If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array.

由此可知:

  1. append() 如果附加到的切片的容量足够,则永远不会复制底层存储。
  2. 如果容量不足,将重新分配数组。

也就是说,给定一个切片 s,您要向其追加 N 个元素,如果 cap(s) - len(s) ≥ N.

则不会进行重新分配

因此我怀疑您的问题不是关于意外的重新分配结果,而是关于在 Go 中实现的切片的 概念。要吸收的代码思想是 append() returns 结果切片值, 你应该在调用后使用 unless 你完全理解后果。

我建议从 this 开始,以充分理解它们。

感谢您的反馈。

因此,获得内存分配控制权的解决方案是明确地进行(这让我想起了 Go 比其他(脚本)语言更像是一种系统语言):

package main

import (
    "fmt"
)

func main() {

    a1 := make([][]int, 3)
    a2 := make([][]int, 3)
    b := [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}
    common1 := make([]int, 0)
    common2 := make([]int, 0, 12) // provide sufficient capacity
    common1 = append(common1, []int{10, 20}...)
    common2 = append(common2, []int{10, 20}...)

    idx := 0
    for _, k := range b {
        a1[idx] = append(common1, k...) // new slice is allocated
        
        a2[idx] = make([]int, len(common2), len(common2)+len(k))
        copy(a2[idx], common2)      // copy & append could probably be
        a2[idx] = append(a2[idx], k...) // combined into a single copy step
        
        idx++
    }

    fmt.Println(a1)
    fmt.Println(a2)
}

输出:

[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]

[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]

https://play.golang.org/p/Id_wSZwb84