在Golang项目开发过程中,结构体是承载业务数据的重要类型,很多时候我们需要复制一个结构体的实例,这时候就会涉及到浅拷贝和深拷贝两种操作方式,两者的核心差异在于是否复制结构体内部引用类型指向的底层数据。

浅拷贝的实现方式
浅拷贝是最简单的结构体拷贝方式,它会直接复制结构体的所有字段值,如果字段是值类型,就会复制对应的值,如果字段是引用类型,只会复制引用地址,不会复制引用指向的实际数据。
直接赋值实现浅拷贝
Golang中结构体直接赋值默认就是浅拷贝,代码示例如下:
package main
import "fmt"
// 定义包含引用类型的结构体
type User struct {
Name string
Age int
Hobbies []string // 切片是引用类型
}
func main() {
// 初始化原始结构体
originUser := User{
Name: "张三",
Age: 20,
Hobbies: []string{"篮球", "跑步"},
}
// 直接赋值实现浅拷贝
copiedUser := originUser
// 修改拷贝结构体的引用类型字段
copiedUser.Hobbies[0] = "足球"
fmt.Println("原始用户爱好:", originUser.Hobbies)
fmt.Println("拷贝用户爱好:", copiedUser.Hobbies)
}
运行上述代码可以看到,修改copiedUser的Hobbies字段后,originUser的Hobbies也发生了变化,这是因为两个结构体的Hobbies字段指向了同一个底层切片。
使用copy函数实现部分浅拷贝
如果只需要复制结构体的部分字段,也可以使用copy函数处理切片类型的字段,但本质还是浅拷贝:
package main
import "fmt"
type Product struct {
ID int
Tags []string
}
func main() {
p1 := Product{ID: 1, Tags: []string{"电子", "数码"}}
p2 := Product{ID: p1.ID}
// 复制切片引用,属于浅拷贝
p2.Tags = p1.Tags
p2.Tags[0] = "家电"
fmt.Println(p1.Tags) // 输出 [家电 数码]
}
深拷贝的实现方式
深拷贝会完整复制结构体的所有层级数据,包括引用类型指向的底层数据,修改拷贝后的结构体不会影响原始结构体,常见的实现方式有以下几种。
手动递归复制实现深拷贝
对于结构简单明确的结构体,可以手动逐字段复制,遇到引用类型就重新创建并复制内容:
package main
import "fmt"
type Address struct {
City string
Street string
}
type Employee struct {
Name string
Addr Address
Skills []string
}
// 手动实现深拷贝函数
func DeepCopyEmployee(origin Employee) Employee {
copyEmp := Employee{
Name: origin.Name,
// 值类型直接复制
Addr: Address{
City: origin.Addr.City,
Street: origin.Addr.Street,
},
}
// 重新创建切片并复制内容
copyEmp.Skills = make([]string, len(origin.Skills))
copy(copyEmp.Skills, origin.Skills)
return copyEmp
}
func main() {
originEmp := Employee{
Name: "李四",
Addr: Address{City: "北京", Street: "长安街"},
Skills: []string{"Go", "Java"},
}
copiedEmp := DeepCopyEmployee(originEmp)
// 修改拷贝后的引用类型字段
copiedEmp.Addr.City = "上海"
copiedEmp.Skills[0] = "Python"
fmt.Println("原始员工地址:", originEmp.Addr.City)
fmt.Println("拷贝员工地址:", copiedEmp.Addr.City)
fmt.Println("原始员工技能:", originEmp.Skills)
fmt.Println("拷贝员工技能:", copiedEmp.Skills)
}
运行上述代码可以看到,修改拷贝后的结构体的引用类型字段,不会影响原始结构体的对应字段,实现了完全的深拷贝。
使用encoding包序列化反序列化实现深拷贝
如果结构体字段较多或者结构复杂,手动复制会比较繁琐,可以利用Golang的序列化机制实现通用深拷贝:
package main
import (
"bytes"
"encoding/gob"
"fmt"
)
type Order struct {
OrderID int
Items []string
Extra map[string]string
}
// 通用深拷贝函数,利用gob序列化反序列化
func DeepCopyByGob(origin interface{}, target interface{}) error {
var buf bytes.Buffer
// 序列化原始对象
enc := gob.NewEncoder(&buf)
if err := enc.Encode(origin); err != nil {
return err
}
// 反序列化到目标对象
dec := gob.NewDecoder(&buf)
return dec.Decode(target)
}
func main() {
originOrder := Order{
OrderID: 1001,
Items: []string{"手机", "耳机"},
Extra: map[string]string{"discount": "9折"},
}
var copiedOrder Order
err := DeepCopyByGob(originOrder, &copiedOrder)
if err != nil {
fmt.Println("深拷贝失败:", err)
return
}
// 修改拷贝后的数据
copiedOrder.Items[0] = "平板"
copiedOrder.Extra["discount"] = "8折"
fmt.Println("原始订单商品:", originOrder.Items)
fmt.Println("拷贝订单商品:", copiedOrder.Items)
fmt.Println("原始订单额外信息:", originOrder.Extra)
fmt.Println("拷贝订单额外信息:", copiedOrder.Extra)
}
这种方式不需要手动处理每个字段,适合复杂结构体的深拷贝,但性能会比手动复制稍差,因为涉及序列化和反序列化的开销。
两种拷贝方式的对比
我们可以通过表格对比浅拷贝和深拷贝的差异:
| 对比项 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 复制范围 | 仅复制结构体顶层字段值 | 复制所有层级的数据 |
| 引用类型处理 | 只复制引用地址 | 复制引用指向的底层数据 |
| 修改影响 | 修改拷贝的引用类型会影响原始对象 | 修改拷贝对象不会影响原始对象 |
| 性能 | 性能高,开销小 | 性能较低,开销大 |
| 适用场景 | 结构体无引用类型或不需要隔离引用数据 | 需要完全隔离原始和拷贝对象的数据 |
注意事项
- 如果结构体包含
sync.Mutex等不可序列化的字段,使用序列化方式实现深拷贝会失败,需要手动处理这类字段。 - 浅拷贝适合临时使用、不需要修改引用类型数据的场景,能减少不必要的内存开销。
- 深拷贝如果结构体嵌套层级很深,手动实现容易遗漏字段,建议优先使用序列化方式或者第三方成熟的深拷贝库。
- 拷贝结构体时需要注意字段的导出性,非导出字段在序列化反序列化时无法被复制,会导致数据丢失。