我需要在 Go 中复制一个切片并阅读文档,我可以使用 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]
您可以随时查看playground。那么为什么我不能复制切片呢?
dst
至少应与您要复制的元素一样大(对于 src
的完整副本,它意味着 len(dst) >= len(src)
)。
b := append([]int{}, a...)
内置 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 Language Specification: Appending to and copying slices 中:
复制的元素数量是 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
tmp := append(make([]int, 0, len(arr)), arr...)
。在底层,append
函数会在每次调用时检查切片的容量,如果即将超出容量,则分配一个新切片。该解决方案具有避免重复重新分配的额外好处。
copy()
大约快 15%
。 OTOH copy()
使用起来更复杂,因为这个 Go Playground 说明了 go.dev/play/p/XDGuVU0xfRy — 此处的基准和结果gist.github.com/mikeschinkel/fbeb291b90970581b4428fbc14e4ab19,因为您无法在共享服务器上可靠地进行基准测试。但是,我通常仍会使用 append()
方法,因为它比 copy()
版本更容易理解并且不太可能破坏,除非性能对于给定的用例绝对至关重要。
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]
与 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
如果您的切片大小相同,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)
会给:
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
克隆为切片的最佳方法是
sClone = append(s[:0:0], s...)
这种实现有两个优点:
如果 s 为 nil,请确保结果 sClone 为 nil,如果 s 不为 nil,则确保结果不为 nil。即使 T 在另一个包中声明,也无需导入 T 类型的包含包
Go 编程语言规范附加和复制切片函数 copy 将切片元素从源 src 复制到目标 dst 并返回复制的元素数量。两个参数必须具有相同的元素类型 T 并且必须可分配给类型为 []T 的切片。复制的元素数量是 len(src) 和 len(dst) 中的最小值。作为一种特殊情况,copy 还接受一个可分配给类型 []byte 的目标参数,其源参数为字符串类型。这种形式将字符串中的字节复制到字节片中。复制(dst,src []T) int 复制(dst []byte,src 字符串) 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]
注意:这是@benlemasurier 证明的错误解决方案
这是一种复制切片的方法。我有点晚了,但有一个比@Dave 更简单、更快的答案。 This 是从 @Dave 之类的代码生成的指令。 These 是我生成的指令。如您所见,说明要少得多。它所做的只是复制切片的 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]
甜美、简单、高性能、无需注意长度、无内存重叠、不同副本
slice2 := append([]int{}, slice1...)
如果你不关心速度:
import "golang.org/x/exp/slices"
tmp := slices.Clone(arr)
使用 Go 1.18 和泛型,现在可以使用包 "golang.org/x/exp/slices"
中的 slices.Clone
复制任何切片。 Playground
copy
不包含用于增加目标切片的逻辑,但还有另一个内置函数可以做到这一点:append
虽然在此示例中最好只分配正确大小首先是 slice,当你已经有一个 slice 并且想通过在末尾添加元素来扩大它时,可以使用append
。