copy函数不能直接复制切片——它仅将源切片元素逐个拷贝到已分配底层数组的目标切片中,目标切片必须预先声明且长度足够,否则实际拷贝数为0。

copy 函数到底能不能直接复制切片?
不能——copy 不是“创建副本”的函数,它只是把源切片的数据**逐元素拷贝到目标切片已分配的底层数组里**。目标切片必须预先存在、长度足够,否则白忙活。
常见错误现象:copy 返回 0,但程序没报错;或者目标切片看起来“空”,其实是没初始化或长度为 0。
- 目标切片
dst必须已声明且有容量(比如用make([]int, len(src))或make([]int, 0, len(src))) - 实际拷贝数量 =
min(len(src), len(dst)),不是按 cap 算 - 如果
dst是nil,copy直接返回 0,不 panic
最安全的切片深拷贝写法(含 []byte 场景)
想得到一个独立、可修改、不影响原切片的新切片?别只靠 copy,得先分配空间。
通用做法:用 make 分配新底层数组,再 copy 填充:
立即学习“go语言免费学习笔记(深入)”;
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src)
对 []byte 更常用(比如处理 HTTP body、JSON 解析后复制):
data := []byte("hello")
backup := make([]byte, len(data))
copy(backup, data)
- 别用
dst := src[:]—— 这只是新 slice header 指向同一底层数组 - 如果要扩容后拷贝(比如追加数据),用
make([]T, 0, cap(src)*2)+copy,避免后续 append 触发 realloc 影响原切片 -
copy对string → []byte有效:copy(dst, []byte(s)),但注意[]byte(s)每次都分配新内存
copy 返回值为什么必须检查?
它返回的是**实际拷贝的元素个数**,不是成功/失败标志。但这个数能立刻暴露你是否搞错了长度逻辑。
典型翻车场景:目标切片长度比源小,却默认“全拷过去了”:
src := []string{"a", "b", "c", "d"}
dst := make([]string, 2) // 注意这里只开了长度 2
n := copy(dst, src) // n == 2,不是 4!
- 永远用
n := copy(dst, src),然后根据n判断是否拷齐(尤其在协议解析、IO 缓冲区填充时) - 网络读取中常见:
n, _ := conn.Read(buf)后,要用copy(dst, buf[:n]),而不是copy(dst, buf) - 如果
n ,说明目标不够——这不是 bug,是设计预期,但你得主动处理,比如分批 or 扩容重试
性能和指针逃逸:为什么有时 copy 比 append 更快?
当目标切片已预分配好(比如从 sync.Pool 拿来的缓冲区),copy 是纯内存搬移,零分配、无逃逸;而 append 在容量不足时会触发底层数组 realloc,产生新分配和 GC 压力。
真实高频场景:日志批量写入、序列化中间缓冲、ring buffer 填充。
- 用
copy前确认 dst 容量 ≥ len(src),否则不如直接append(dst[:0], src...) - benchmark 时注意:如果 dst 是局部变量且长度固定,编译器可能优化掉部分 copy 开销;但跨函数传递时,逃逸分析更关键
- 对小切片(copy 和手动 for 循环性能几乎没差别;别过早优化,先确保语义正确
真正容易被忽略的点:copy 的源和目标底层数组**不能重叠且有交集**(比如 copy(a[1:], a)),否则行为未定义——Go 1.22 起会检测并 panic,但旧版本可能静默出错。只要不是故意做滑动窗口操作,就老实用两个独立 slice。










