Golang:附加切片或没有分配

时间:2015-11-18 14:55:46

标签: go append slice

Go append()函数仅在给定切片的容量不足时才分配新的切片数据(另请参阅:https://stackoverflow.com/a/28143457/802833)。这可能导致意外行为(至少对我来说是一个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中强制分配新切片数据的(idomatic)方式是什么,或者更确切地说是为了确保append()的切片参数保持不变?

2 个答案:

答案 0 :(得分:5)

您可能会对Go中的切片工作方式保持错误的认识。

将元素附加到切片时,对append()的调用将返回一个新切片。如果没有发生重新分配,则两个切片值(您调用{1}}的切片值和返回的切片值 - 共享相同的支持数组它们将具有不同的长度;观察:

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) == 3和第二次调用len(c) == 3 ower写了第一个所做的事情,因为所有切片共享相同的底层数组。< / p>

关于支持数组的重新分配,the spec很明确:

  

如果s的容量不足以容纳附加值,则append会分配一个适合现有切片元素和附加值的新的足够大的基础数组。否则,追加重新使用底层数组。

由此得出:

    如果被覆盖的切片的容量足够,则
  1. append()永远不会复制底层存储。
  2. 如果容量不足,则会重新分配阵列。
  3. 也就是说,如果您想要追加append()元素的切片s,则iff N将无法重新分配。

    因此我怀疑你的问题不是关于意外的重新分配结果,而是关于Go中实现的切片的概念。要吸收的代码理念是cap(s) - len(s) ≥ N 返回在调用之后应该使用的结果切片值,除非您完全理解反响。

    我建议从this开始,以完全理解它们。

答案 1 :(得分:0)

Thanx感谢您的反馈。

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

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