为什么我不能用“ copy ()”复制一个切片?

我需要在 Go 中复制一个片段,然后阅读文档,这里有一个 收到函数可供我使用。

内置的复制函数将元素从源切片复制到 (作为特殊情况,它还将从 源和目标可能重叠。 复制返回复制的元素数量,这将是最小值 Len (src)和 Len (dst)。

但是当我这么做的时候:

arr := []int{1, 2, 3}
tmp := []int{}
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

我的 tmp和以前一样是空的(我甚至尝试使用 arr, tmp) :

[]
[1 2 3]

你可以检查它去 游乐场。那么为什么我不能复制一片?

153925 次浏览

如果你的切片大小相同,会成功的:

arr := []int{1, 2, 3}
tmp := []int{0, 0, 0}
i := copy(tmp, arr)
fmt.Println(i)
fmt.Println(tmp)
fmt.Println(arr)

会给出:

3
[1 2 3]
[1 2 3]

来自《 切片: 使用和内部》 :

Copy 函数支持在不同长度的片之间进行复制(它只复制少量的元素)

通常的例子是:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

围棋编程语言规范

附加和复制切片

函数将切片元素从源 src 复制到 返回复制的元素数 参数必须具有相同的元素类型 T,并且必须可赋值给 类型为[] T 的一片。复制的元素数目是 Len (src)和 len (dst) 可分配给带源参数的类型[] byte 的目标参数 此表单将字节从字符串复制到 字节切片。

copy(dst, src []T) int
copy(dst []byte, src string) int

tmp需要足够的空间来容纳 arr,

package main


import "fmt"


func main() {
arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)
}

产出:

[1 2 3]
[1 2 3]

内建的 copy(dst, src)复制 min(len(dst), len(src))元素。

因此,如果您的 dst为空(len(dst) == 0) ,则不会复制任何内容。

试试 tmp := make([]int, len(arr))(去游乐场) :

arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)

产出(如预期) :

[1 2 3]
[1 2 3]

遗憾的是,builtin软件包中没有这方面的文档,但是在 < strong > Go 语言规范: 附加和复制切片 中有这方面的文档:

复制的元素数量是 len(src)len(dst)的最小值。

编辑:

最后,copy()的文件已经更新,其中包含将复制源和目的地的最小长度:

Copy 返回所复制的元素数,即 len (src)和 len (dst)的 最低限度

另一个简单的方法是使用 append,它将在进程中分配切片。

arr := []int{1, 2, 3}
tmp := append([]int(nil), arr...)  // Notice the ... splat
fmt.Println(tmp)
fmt.Println(arr)

产出(如预期) :

[1 2 3]
[1 2 3]

正如在下面的注释中指出的,如果从一开始就没有正确地调整片的大小,append可能会分配多余的内存。一个不错的解决方案是预先分配一部分正确的容量,如下所示:

tmp := append(make([]int, 0, len(arr)), arr...)

所以复制数组 arr的简写应该是 append(make([]int, 0, len(arr)), arr...)

Https://play.golang.org/p/xwevi1chgrd

Copy ()运行的 dst 和 src 的长度最小,因此必须将 dst 初始化为所需的长度。

A := []int{1, 2, 3}
B := make([]int, 3)
copy(B, A)
C := make([]int, 2)
copy(C, A)
fmt.Println(A, B, C)

产出:

[1 2 3] [1 2 3] [1 2]

您可以使用 append ()初始化并复制一行中的所有元素到 nil 片。

x := append([]T{}, []...)

例如:

A := []int{1, 2, 3}
B := append([]int{}, A...)
C := append([]int{}, A[:2]...)
fmt.Println(A, B, C)

产出:

[1 2 3] [1 2 3] [1 2]

对于超过1,000个元素,使用 append。实际上低于1000的差异可能会被忽略,除非你有很多片,否则还是按经验法则来吧。

BenchmarkCopy1-4                50000000            27.0 ns/op
BenchmarkCopy10-4               30000000            53.3 ns/op
BenchmarkCopy100-4              10000000           229 ns/op
BenchmarkCopy1000-4              1000000          1942 ns/op
BenchmarkCopy10000-4              100000         18009 ns/op
BenchmarkCopy100000-4              10000        220113 ns/op
BenchmarkCopy1000000-4              1000       2028157 ns/op
BenchmarkCopy10000000-4              100      15323924 ns/op
BenchmarkCopy100000000-4               1    1200488116 ns/op
BenchmarkAppend1-4              50000000            34.2 ns/op
BenchmarkAppend10-4             20000000            60.0 ns/op
BenchmarkAppend100-4             5000000           240 ns/op
BenchmarkAppend1000-4            1000000          1832 ns/op
BenchmarkAppend10000-4            100000         13378 ns/op
BenchmarkAppend100000-4            10000        142397 ns/op
BenchmarkAppend1000000-4            2000       1053891 ns/op
BenchmarkAppend10000000-4            200       9500541 ns/op
BenchmarkAppend100000000-4            20     176361861 ns/op

注意: @benlemasurier 证明了这是一个错误的解决方案

这里有一个复制一个切片的方法。我有点迟到了,但是有一个更简单、更快的答案。这个是由像@Dave’s 这样的代码生成的指令。这些是我的指令。正如你看到的,说明书少得多。它只复制 append(slice),它复制切片。这个代码:

package main


import "fmt"


func main() {
var foo = []int{1, 2, 3, 4, 5}
fmt.Println("foo:", foo)
var bar = append(foo)
fmt.Println("bar:", bar)
bar = append(bar, 6)
fmt.Println("foo after:", foo)
fmt.Println("bar after:", bar)
}

输出结果如下:

foo: [1 2 3 4 5]
bar: [1 2 3 4 5]
foo after: [1 2 3 4 5]
bar after: [1 2 3 4 5 6]

克隆切片的最好方法是

sClone = append(s[:0:0], s...)

这种做法有两个好处:

  1. 如果 s 为 nil,则确保结果 sClone 为 nil,而不是 nil 如果 s 不是零。

  2. 中声明了 T,也不需要导入包含 T 类型的包 另一个包裹

甜,简单,性能,不需要注意长度,没有记忆重叠,不同的副本

slice2 := append([]int{}, slice1...)

如果你不在乎速度:

import "golang.org/x/exp/slices"


tmp := slices.Clone(arr)

有了 Go 1.18和泛型,现在可以用 slices.Clone从软件包 "golang.org/x/exp/slices".游乐场复制任何片

只需要为那三个实现切片复制的方法做基准测试

  • CloneWithAppend上的 append
  • CloneWithCopy上的 copy
  • 对于 CloneWithAny上的通用 any,使用 append
func CloneWithAppend(b []byte) []byte {
if b == nil {
return nil
}
return append([]byte{}, b...)
}


func CloneWithCopy(b []byte) []byte {
if b == nil {
return nil
}
tmp := make([]byte, len(b))
copy(tmp, b)
return tmp
}


func CloneWithAny[B ~[]T, T any](b B) B {
if b == nil {
return nil
}
return append([]T{}, b...)
}

基准代码

var testSlice = []byte("qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM098765432112345678901234567890")


func BenchmarkCloneWithAppend(b *testing.B) {
for i := 0; i < b.N; i++ {
CloneWithAppend(testSlice)
}
}


func BenchmarkCloneWithCopy(b *testing.B) {
for i := 0; i < b.N; i++ {
CloneWithCopy(testSlice)
}
}


func BenchmarkCloneWithAny(b *testing.B) {
for i := 0; i < b.N; i++ {
CloneWithAny(testSlice)
}
}

结果

goarch: amd64
pkg: test
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkCloneWithAppend-12     28700232                41.50 ns/op
BenchmarkCloneWithCopy-12       32453222                30.98 ns/op
BenchmarkCloneWithAny-12        31737926                41.68 ns/op


似乎与 copy方法有更好的性能。


注意,在 Golang,按照这个 承诺和相关的建议 字节,字符串: add Clone,下一次发布时将增加 pkgbytesfunc Clone([]uint8) []uint8

// Clone returns a copy of b[:len(b)].
// The result may have additional unused capacity.
// Clone(nil) returns nil.
func Clone(b []byte) []byte {
if b == nil {
return nil
}
return append([]byte{}, b...)
}