本文主要介绍Go实现浅拷贝和深拷贝,以及工具库mohae/deepcopy。
定义
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等
浅拷贝
go使用copy内置函数将源片中的元素复制到目标切片,如果拷贝的 结构中不包含指针是没问题,否则则数据源和拷贝之间对应指针会共同指向同一块内存。
func shallowCopyReference() {
brazil := []*Player{{"Neymar"}, {"Messi"}}
argentina := make([]*Player, len(brazil))
copy(argentina, brazil)
fmt.Printf("%p, %p\n", brazil, brazil[0])
fmt.Printf("%p, %p\n", argentina, argentina[0])
brazil[0].Name = "Anthony"
fmt.Printf("%p, %v, %p\n", brazil, brazil[0], brazil[0])
fmt.Printf("%p, %v, %p\n", argentina, argentina[0], brazil[0])
}
//output
0xc0000412e0, 0xc0000412f0
0xc000041310, 0xc0000412f0
0xc0000412e0, &{Anthony}, 0xc0000412f0
0xc000041310, &{Anthony}, 0xc0000412f0
如上,修改了src(拷贝原始结构)的指针对象,dst(拷贝目标结构)的值也被改动了,显然是不行的。
深拷贝
方法一:gob、json 序列化成字节序列再反序列化生成克隆对象
- gob序列化
func deepCopyByGobExample() {
players := append([]*Player{}, &Player{Name: "Neymar"}, &Player{Name: "Messi"})
brazil := &Team{Players: players}
argentina := &Team{}
err := deepCopyByGob(brazil, argentina)
if err != nil {
fmt.Errorf("deepcopy error:", err)
}
fmt.Printf("%v,%v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v,%v, %p\n", "argentina", argentina.Players[0], argentina.Players[0])
brazil.Players[0].Name = "Anthony"
fmt.Printf("%v, %v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v, %v, %p\n", "argentina", argentina.Players[0], brazil.Players[0])
}
func deepCopyByGob(dst, src *Team) error {
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(src); err != nil {
return err
}
return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}
//output
brazil,&{Neymar}, 0xc000184ff0
argentina,&{Neymar}, 0xc000185330
brazil, &{Anthony}, 0xc000184ff0
argentina, &{Neymar}, 0xc000184ff0
- json序列化
func deepcopyByJsonExample() {
players := append([]*Player{}, &Player{Name: "Neymar"}, &Player{Name: "Messi"})
brazil := &Team{Players: players}
argentina := &Team{}
err := deepCopyByJson(brazil, argentina)
if err != nil {
fmt.Errorf("deepcopy error:", err)
}
fmt.Printf("%v,%v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v,%v, %p\n", "argentina", argentina.Players[0], argentina.Players[0])
brazil.Players[0].Name = "Anthony"
fmt.Printf("%v, %v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v, %v, %p\n", "argentina", argentina.Players[0], brazil.Players[0])
}
func deepCopyByJson(src, dst *Team) error {
if tmp, err := json.Marshal(src); err != nil {
return err
} else {
err = json.Unmarshal(tmp, dst)
return err
}
}
//output
brazil,&{Neymar}, 0xc0000412e0
argentina,&{Neymar}, 0xc000041490
brazil, &{Anthony}, 0xc0000412e0
argentina, &{Neymar}, 0xc0000412e0
可以看到deepcopy出来的指针地址不一样,那么都dst的修改就不会影响到src了。
方法二:使用第三方工具库
DeepCopy对事物进行深度复制:未导出的字段值不会被复制。 feature:
- copy slice
- copy map
func deepCopyExample() {
brazil := &Team{Players: append([]*Player{}, &Player{Name: "Neymar"}, &Player{Name: "Messi"})}
argentinaIfc := deepcopy.Copy(brazil)
argentina := argentinaIfc.(*Team)
fmt.Printf("%v,%v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v,%v, %p\n", "argentina", argentina.Players[0], argentina.Players[0])
brazil.Players[0].Name = "Anthony"
fmt.Printf("%v, %v, %p\n", "brazil", brazil.Players[0], brazil.Players[0])
fmt.Printf("%v, %v, %p\n", "argentina", argentina.Players[0], brazil.Players[0])
}
//output
brazil,&{Neymar}, 0xc0000412e0
argentina,&{Neymar}, 0xc000041330
brazil, &{Anthony}, 0xc0000412e0
argentina, &{Neymar}, 0xc0000412e0
小结
浅拷贝是值的拷贝,如果属性是引用,拷贝的就是内存地址,需要使用深拷贝。深拷贝的实现原理本质上是通过反射实现。通过将源对象转换成接口,再对接口通过反射判断其类型。所以可以通过序列化成gob、json等来实现。工具库mohae/deepcopy可以对切片、map、结构体、接口进行深拷贝,是个不错的选择。