为什么我不能使用`copy()`函数复制一个切片?

huangapple go评论85阅读模式
英文:

Why can't I duplicate a slice with `copy()`?

问题

我需要在Go语言中复制一个切片,并且在阅读文档时发现有一个可用的copy函数。

copy内置函数将源切片的元素复制到目标切片中。(作为特例,它还可以将字符串的字节复制到字节切片中。)源和目标可能重叠。copy返回复制的元素数量,这将是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]

你可以在Go playground上检查它。那么为什么我不能复制一个切片呢?

英文:

I need to make a copy of a slice in Go and reading the docs there is a copy function at my disposal.

> The copy built-in function copies elements from a source slice into a
> destination slice. (As a special case, it also will copy bytes from a
> string to a slice of bytes.) The source and destination may overlap.
> Copy returns the number of elements copied, which will be the minimum
> of len(src) and len(dst).

But when I do:

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

My tmp is empty as it was before (I even tried to use arr, tmp):

[]
[1 2 3]

You can check it on go playground. So why can not I copy a slice?

答案1

得分: 320

内置函数copy(dst, src)会复制min(len(dst), len(src))个元素。

所以如果你的dst是空的(len(dst) == 0),则不会复制任何内容。

尝试使用tmp := make([]int, len(arr))Go Playground):

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包中没有被记录,但在Go语言规范:向切片追加和复制中有记录:

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

编辑:

最终copy()函数的文档已经更新,现在包含了源和目标最小长度被复制的事实:

> Copy返回被复制的元素数量,这个数量将是len(src)len(dst)中的最小值

英文:

The builtin copy(dst, src) copies min(len(dst), len(src)) elements.

So if your dst is empty (len(dst) == 0), nothing will be copied.

Try tmp := make([]int, len(arr)) (Go Playground):

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

Output (as expected):

[1 2 3]
[1 2 3]

Unfortunately this is not documented in the builtin package, but it is documented in the Go Language Specification: Appending to and copying slices:

> The number of elements copied is the minimum of len(src) and len(dst).

Edit:

Finally the documentation of copy() has been updated and it now contains the fact that the minimum length of source and destination will be copied:

> Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).

答案2

得分: 52

另一种简单的方法是使用append函数来分配切片的空间。

arr := []int{1, 2, 3}
tmp := append([]int(nil), arr...)  // 注意这里的...操作符
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

英文:

Another simple way to do this is by using append which will allocate the slice in the process.

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

Output (as expected):

[1 2 3]
[1 2 3]

As pointed out in the comments below, append may allocate excess memory if the slice isn't sized correctly to begin with. A nice solution to this is to preallocate a slice of the right capacity, like so:

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

So a shorthand for copying array arr would be append(make([]int, 0, len(arr)), arr...)

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

答案3

得分: 13

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()函数将所有元素初始化并复制到一个空切片中。

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]

与分配+复制(allocation+copy())相比,对于大于1,000个元素的情况,使用append()函数。实际上,在1,000以下的情况下,差异可能可以忽略不计,除非您有很多切片。

以下是一些基准测试的结果:

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
英文:

The copy() runs for the least length of dst and src, so you must initialize the dst to the desired length.

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

Output:

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

You can initialize and copy all elements in one line using append() to a nil slice.

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

Example:

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

Output:

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

Comparing with allocation+copy(), for greater than 1,000 elements, use append. Actually bellow 1,000 the difference may be neglected, make it a go for rule of thumb unless you have many slices.

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

答案4

得分: 12

如果你的切片大小相同,它将起作用

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]

来自"Go切片:用法和内部原理":

copy函数支持在不同长度的切片之间进行复制(它只会复制到较小的元素数量

通常的例子是:

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

If your slices were of the same size, it would work:

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

Would give:

3
[1 2 3]
[1 2 3]

From "Go Slices: usage and internals":

> The copy function supports copying between slices of different lengths (it will copy only up to the smaller number of elements)

The usual example is:

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

答案5

得分: 12

最佳的切片克隆方法是:

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

这种实现方式有两个优点:

  1. 确保如果 s 为 nil,则结果 sClone 也为 nil;如果 s 不为 nil,则 sClone 不为 nil。

  2. 即使类型 T 声明在另一个包中,也无需导入 T 所在的包。

英文:

The best way to clone as slice is

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

This implementation has two advantage:

  1. make sure that the result sClone is nil if s is nil, and is not nil
    if s is not nil.

  2. No need to import the containing package of type T even if T is declared in
    another package

答案6

得分: 2

《Go编程语言规范》

追加和复制切片

函数copy将源切片src的元素复制到目标切片dst,并返回复制的元素数量。两个参数必须具有相同的元素类型T,并且必须可分配给类型[]T的切片。复制的元素数量是len(src)和len(dst)中的最小值。作为特例,copy还接受一个目标参数,可分配给类型[]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]
英文:

> The Go Programming Language Specification
>
> Appending to and copying slices
>
> The function copy copies slice elements from a source src to a
> destination dst and returns the number of elements copied. Both
> arguments must have identical element type T and must be assignable to
> a slice of type []T. The number of elements copied is the minimum of
> len(src) and len(dst). As a special case, copy also accepts a
> destination argument assignable to type []byte with a source argument
> of a string type. This form copies the bytes from the string into the
> byte slice.
>
> copy(dst, src []T) int
> copy(dst []byte, src string) int

tmp needs enough room for arr. For example,

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)
}

Output:

[1 2 3]
[1 2 3]

答案7

得分: 2

如果您不关心速度:

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

tmp := slices.Clone(arr)

使用Go 1.18和泛型,现在可以使用golang.org/x/exp/slices包中的slices.Clone函数来复制任何切片。Playground

英文:

If you don't care about the speed:

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

tmp := slices.Clone(arr)

With Go 1.18 and generics, any slices now could be copied with slices.Clone from package "golang.org/x/exp/slices". Playground

答案8

得分: 1

甜美、简单、高效,无需担心长度,无内存重叠,不同的副本

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

Sweet, Simple, Performant, No need to be careful of length, No Memory overlap, Different copies

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

答案9

得分: 0

> **注意:**这是一个错误的解决方案,正如@benlemasurier证明的那样。

这是一种复制切片的方法。我有点晚了,但是比@Dave的答案更简单、更快。这个是从类似@Dave的代码生成的指令。这些是由我的代码生成的指令。你可以看到指令要少得多。它所做的就是执行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]
英文:

> NOTE: This is an incorrect solution as @benlemasurier proved

Here is a way to copy a slice. I'm a bit late, but there is a simpler, and faster answer than @Dave's. This are the instructions generated from a code like @Dave's. These is the instructions generated by mine. As you can see there are far fewer instructions. What is does is it just does append(slice), which copies the slice. This code:

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)
}

Outputs this:

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]

答案10

得分: 0

只需对实现切片复制的这三种方法进行基准测试:

  • CloneWithAppend中使用append
  • CloneWithCopy中使用copy
  • CloneWithAny中使用通用的anyappend
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方法的性能更好。


注意,根据这个提交和相关提案bytes, strings: add Clone,在下一个Golang版本中将添加bytes包中的func Clone([]uint8) []uint8

// Clone返回b[:len(b)]的副本。
// 结果可能有额外的未使用容量。
// Clone(nil)返回nil。
func Clone(b []byte) []byte {
	if b == nil {
		return nil
	}
	return append([]byte{}, b...)
}
英文:

Just do benchmark for those three methods which implement slice copy

  • with append on CloneWithAppend
  • with copy on CloneWithCopy
  • with append for generic any on CloneWithAny
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...)
}

Benchmark codes

var testSlice = []byte(&quot;qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM098765432112345678901234567890&quot;)

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

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

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

Results

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

It seems the with copy method has better performance.


Note, func Clone([]uint8) []uint8 of pkg bytes would be added in Golang next release per this commit and related proposal bytes, strings: add Clone

// 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...)
}

答案11

得分: 0

感谢回答者,如果有人需要复制多个列表,这个通用方法可能会有所帮助(正如在文档中所说,Go 从 1.18 版本开始引入了对泛型的本地支持):

func CopyList[T any](list []T) []T {
    newList := make([]T, len(list))
    copy(newList, list)
    return newList
}

用法:copiedList := CopyList(originalList)

英文:

Thanks to the answerers, if someone needs to copy many lists, this generic method may helps (As said in the documentation, Go introduced native support for generics starting from version 1.18) :

func CopyList[T any](list []T) []T {
	newList := make([]T, len(list))
	copy(newList, list)
	return newList
}

usage : copiedList := CopyList(originalList)

huangapple
  • 本文由 发表于 2015年5月12日 13:33:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/30182538.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定