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) == 0
、len(b) == 3
、len(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.
由此可知:
append()
如果附加到的切片的容量足够,则永远不会复制底层存储。
- 如果容量不足,将重新分配数组。
也就是说,给定一个切片 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]]
Go 的 append()
函数仅在给定切片的容量不足时分配新的切片数据(另请参阅:
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) == 0
、len(b) == 3
、len(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.
由此可知:
append()
如果附加到的切片的容量足够,则永远不会复制底层存储。- 如果容量不足,将重新分配数组。
也就是说,给定一个切片 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]]