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

Golang:使用或不使用分配附加切片

Golang:使用或不使用分配附加切片

Go
慕森王 2021-11-22 18:08:32
Go 的append()函数仅在给定切片的容量不足时分配新的切片数据。这可能会导致意外行为(至少对我作为 golang 新手而言):package mainimport (    "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 中强制分配新切片数据或更精确地确保切片参数 toappend()保持不变的(惯用的)方式是什么?
查看完整描述

2 回答

?
慕斯709654

TA贡献1840条经验 获得超5个赞

你可能对 Go 中切片的工作方式有一个错误的看法。


当您将元素附加到切片时,调用会append()返回一个新切片。如果没有发生重新分配,两个切片值——你调用的值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) == 0, len(b) == 3, len(c) == 3, 和第二次调用append()owerw 写了第一个所做的事情,因为所有的切片共享相同的底层数组。


关于后备数组的重新分配,规范很明确:


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

由此可知:

  1. append() 如果被附加到的切片的容量足够,则永远不要复制底层存储。

  2. 如果没有足够的容量,阵列将被重新分配。

也就是说,给定一个s要向其附加N元素的切片,如果 iff 则不会进行重新分配cap(s) - len(s) ≥ N

因此,我怀疑您的问题不在于意外的重新分配结果,而在于 Go 中实现的切片概念。要吸收的代码想法是append() 返回结果切片值,除非您完全了解影响,否则您应该在调用后使用该值。

我建议从这里开始以完全理解它们。


查看完整回答
反对 回复 2021-11-22
?
忽然笑

TA贡献1806条经验 获得超5个赞

因此,控制内存分配的解决方案是明确地进行(这让我想起 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


查看完整回答
反对 回复 2021-11-22
  • 2 回答
  • 0 关注
  • 158 浏览
慕课专栏
更多

添加回答

举报

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