go grpc proto message 如何进行深拷贝
目录
背景
数据拷贝在开发过程中非常常见,在 go 语言中如果是简单的对指针对象进行引用,那么修改新对象会同时影响引用对象,这是我们不想看到的
浅拷贝
(和拷贝的对象是同一块数据地址)拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址
// 原始消息
req := &pb.UserRequest{Id: 1001, Name: "张三"}
// ❌ 错误拷贝方式
copyReq := req
copyReq.Name = "李四" // 原始req也被修改!
// ✅ 深拷贝后修改
safeCopy := DeepCopy(req)
safeCopy.Name = "王五" // 原始req不受影响
深拷贝
(全新独立对象)拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
方案一:proto.Clone(官方推荐)
import "google.golang.org/protobuf/proto"
func ProtoClone(src proto.Message) proto.Message {
return proto.Clone(src)
}
方案二:序列化法(通用但低效)
func MarshalCopy(src proto.Message) proto.Message {
data, _ := proto.Marshal(src)
dst := reflect.New(reflect.TypeOf(src).Elem()).Interface().(proto.Message)
proto.Unmarshal(data, dst)
return dst
}
常见问题
1. 为什么不能用 json 序列化反序列化的方式来实现深拷贝 proto message
proto 的实现原理和 json 不一样,如果用 json 进行深拷贝,会出现数据丢失、数据精度等问题,而且还存在性能问题,不建议使用,建议使用官方推荐的 Marshal、Unmarshal、Clone 函数