go 的 copy 函数仅浅拷贝,不递归处理引用类型;深拷贝需手动实现或用第三方库,判断依据是数据生命周期与并发修改需求。

Go 里 copy 函数只能做浅拷贝,别指望它复制嵌套结构
copy 函数只复制顶层元素的值,对切片、map、指针、struct 字段里的引用类型完全不递归处理。比如你有一个 []*int 或 []struct{ Name string; Data []byte },copy 后新切片里的指针或子切片仍指向原内存地址。
常见错误现象:
修改副本中某个 struct 的 Data 字段,原切片对应项也跟着变;或者把副本传给 goroutine 后,主线程改了原切片底层数组,导致数据竞争。
- 适用场景:纯值类型切片(如
[]int、[]string)的快速复制,或已知内部无引用需隔离的场景 - 参数差异:
copy(dst, src)要求dst可寻址且长度 ≥src长度,否则只复制到dst容量上限 - 性能影响:零分配、O(n) 时间,但掩盖了共享风险 —— 看似快,出问题更难查
深拷贝必须手动实现或借助第三方库,标准库不提供
Go 没有类似 Python 的 copy.deepcopy 或 Java 的序列化克隆机制。反射能做但开销大、不安全(比如无法处理 unexported 字段)、且不能跨包处理私有结构。
使用场景取决于数据结构复杂度:
立即学习“go语言免费学习笔记(深入)”;
- 简单 struct + 值类型字段:直接赋值(
new := old)就足够,本质是深拷贝 - 含 slice/map/ptr 的 struct:必须逐字段处理,尤其注意
map要用make+for range重建,slice要make+copy - 嵌套多层或动态结构:考虑
gob编码解码(注意性能和字段导出限制),或用github.com/jinzhu/copier这类轻量库(它用反射,但跳过私有字段并支持 tag 控制)
示例(安全的手动深拷贝):
func deepCopySliceOfStructs(src []Item) []Item {
dst := make([]Item, len(src))
for i := range src {
dst[i] = src[i] // Item 是全值类型字段才成立
}
return dst
}
切片复制时底层数组共享是默认行为,不是 bug 是设计
Go 切片本质是三元组:{ptr, len, cap}。copy、append、甚至 s[1:3] 子切片,只要没触发扩容,都可能复用同一底层数组。这是性能关键,但也意味着「副本」未必独立。
容易踩的坑:
- 把函数内局部切片返回后,在外面继续
append—— 可能意外改到其他变量共享的底层数组 - 用
copy(dst[:0], src)清空再填,但dst容量很大,后续 append 仍在原数组上扩,污染上游 - 并发读写同一底层数组的不同切片,没加锁 → data race(
go run -race能抓到)
解决思路:需要隔离时,显式创建新底层数组:
newSlice := make([]T, len(old)) copy(newSlice, old)或用
append([]T(nil), old...)(更简洁,但语义稍隐晦)
什么时候该用深拷贝?看数据生命周期和所有权边界
不是所有复制都需要深拷贝。核心判断依据是:副本是否会在原数据失效后继续使用,或是否会被并发修改。
- HTTP handler 中接收 JSON 解析出的
[]User,转给异步任务处理 → 必须深拷贝,避免 handler 返回后底层内存被 GC 或重用 - 配置切片在 init 阶段生成,只读全局使用 → 浅拷贝够用,甚至直接共享
- goroutine 间传递切片且只读 → 加
sync.RWMutex比深拷贝更高效
最易被忽略的一点:深拷贝本身不解决竞态,只是移除共享。如果深拷贝后的数据又被多个 goroutine 无锁读写,问题照旧。真正的边界在于「谁负责修改」和「修改是否可预期」。










