为啥我不能用 `copy()` 复制切片?

Posted

技术标签:

【中文标题】为啥我不能用 `copy()` 复制切片?【英文标题】:Why can't I duplicate a slice with `copy()`?为什么我不能用 `copy()` 复制切片? 【发布时间】:2015-07-22 20:02:32 【问题描述】:

我需要在 Go 中复制一个切片并阅读文档,我可以使用 copy 函数。

copy 内置函数将元素从源切片复制到 目标切片。 (作为一种特殊情况,它还会从 字符串转换为字节片。)源和目标可能重叠。 Copy 返回复制的元素数量,这将是最小值 len(src) 和 len(dst)。

但是当我这样做时:

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

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

[]
[1 2 3]

您可以随时查看playground。那么为什么我不能复制切片呢?

【问题讨论】:

谢谢大家,很遗憾没有注意到切片应该是一样长的。 不一定相同,但dst 至少应该与您要复制的元素一样大(对于src 的完整副本,它意味着len(dst) >= len(src))。 b := append([]int, a...) 【参考方案1】:

甜美、简单、高效、无需注意长度、无内存重叠、不同副本

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

【讨论】:

【参考方案2】:

克隆为切片的最佳方法是

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

这种实现有两个优点:

    如果 s 为 nil,则确保结果 sClone 为 nil,并且不为 nil 如果 s 不为零。

    即使 T 声明在 另一个包

【讨论】:

【参考方案3】:

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

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

输出(如预期):

[1 2 3]
[1 2 3]

正如在下面的 cmets 中所指出的,如果切片开始时的大小不正确,append 可能会分配多余的内存。一个很好的解决方案是预先分配正确容量的切片,如下所示:

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

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

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

【讨论】:

这里的问题是,在更大的实际示例中,append 将分配多余的内存——除非这个数组稍后通过一些进一步的处理来填充容量——因为它是为重复调用的有效重新分配。 play.golang.org/p/5_6618xnXn 观察到 cap(x) 增加到 12,而不是 10。现在看看当 1 个值添加到 1048576 个值时会发生什么play.golang.org/p/nz32JPehhl 容量跃升 2048 个插槽到 1050624,只容纳一个额外的值。 另一个允许在线解决方案同时解决@j.andrewshusta 提出的问题的选项是初始化输入切片的容量,如下所示:tmp := append(make([]int, 0, len(arr)), arr...)。在后台,append 函数在每次调用时检查切片的容量,如果容量即将被超过,则分配一个新切片。此解决方案具有避免重复重新分配的额外好处。【参考方案4】:

注意:这是一个不正确的解决方案,正如@benlemasurier 所证明的那样

这是一种复制切片的方法。我有点晚了,但有一个比@Dave 更简单、更快的答案。 This 是从 @Dave 之类的代码生成的指令。 These 是我生成的指令。如您所见,说明要少得多。它所做的只是复制切片的append(slice)。这段代码:

package main

import "fmt"

func main() 
    var foo = []int1, 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]

【讨论】:

这是不正确的,如下所示:play.golang.org/p/q3CoEoaid6d。预期的输出应该反映@Dave 的回答:play.golang.org/p/mgdJ4voSlpd @benlemasurier -- 嗯...看来你是对的!谢谢你告诉我!【参考方案5】:

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

A := []int1, 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 := []int1, 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

【讨论】:

append 应该用于数组将通过重复调用而增加的情况,因为它会乐观地分配多余的容量来预期这一点。在结果数组应创建为精确大小且不再重新分配的情况下,每个输入数组应使用一次副本。 play.golang.org/p/0kviwKmGzx你没有分享产生这些结果的基准代码,所以我不能确认或否认它的有效性,但它忽略了这个更重要的方面。 你的意思是 'slice' 而不是 array。它们是不同的东西。【参考方案6】:

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

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

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

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

输出(如预期):

[1 2 3]
[1 2 3]

不幸的是,builtin 包中没有记录,但Go Language Specification: Appending to and copying slices 中记录了它:

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

编辑:

最后,copy() 的文档已更新,现在包含将复制源和目标的最小长度的事实:

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

【讨论】:

总而言之,copy 不包含在目标切片太小时增大目标切片的逻辑,但还有另一个内置函数可以做到这一点:append 虽然在此示例中最好首先分配正确大小的切片,append 可以在您已经有切片并希望通过在末尾添加元素来扩大切片时使用。 但是为什么在复制一个无限大小的切片时我必须创建一个有界大小的切片?【参考方案7】:

如果您的切片大小相同,it would work:

arr := []int1, 2, 3
tmp := []int0, 0, 0
i := copy(tmp, arr)
fmt.Println(i)
fmt.Println(tmp)
fmt.Println(arr)

愿意:

3
[1 2 3]
[1 2 3]

来自“Go Slices: usage and internals”:

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

通常的例子是:

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

【讨论】:

【参考方案8】:

The Go Programming Language Specification

Appending to and copying slices

函数 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 := []int1, 2, 3
    tmp := make([]int, len(arr))
    copy(tmp, arr)
    fmt.Println(tmp)
    fmt.Println(arr)

输出:

[1 2 3]
[1 2 3]

【讨论】:

以上是关于为啥我不能用 `copy()` 复制切片?的主要内容,如果未能解决你的问题,请参考以下文章

为啥通过切片分配到列表末尾之后不会引发 IndexError? [复制]

为啥python的列表切片不会产生索引越界错误? [复制]

Python列表操作:遍历、range()、列表解析、列表切片、列表复制、元组

在 golang 中如何使用切片进行复制? [关闭]

如何惯用地复制切片?

为啥使用 copy() 时字符串和数组的处理方式不同? [复制]