【Go】Go语言中深拷贝和浅拷贝
✨✨ 欢迎大家来到景天科技苑✨✨
🎈🎈 养成好习惯,先赞后看哦~🎈🎈
🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。所属的专栏:Go语言开发零基础到高阶实战
景天的主页:景天科技苑
文章目录
- Go语言中深拷贝和浅拷贝
- 一、深拷贝和浅拷贝的基本概念
- 1.1 深拷贝(Deep Copy)
- 1.2 浅拷贝(Shallow Copy)
- 二、深拷贝和浅拷贝的区别
- 2.1 数据复制
- 2.2 对象关联
- 2.3 内存占用
- 三、Go语言中深拷贝和浅拷贝的使用场景
- 3.1 深拷贝的使用场景
- 3.2 浅拷贝的使用场景
- 四、Go语言中深拷贝和浅拷贝的实现方法
- 4.1 浅拷贝的实现
- 示例1:引用类型的浅拷贝
- 示例2:结构体的浅拷贝
- 4.2 深拷贝的实现
- 4.2.1 使用`json.Marshal`和`json.Unmarshal`
- 4.2.2 递归深拷贝
- 五、总结
Go语言中深拷贝和浅拷贝
在Go语言中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种常见的对象复制方式,它们的主要区别在于是否真正获取到了被拷贝对象的单独掌控权,从而避免互相影响的问题。本文将结合实际案例,详细阐述Go语言中深拷贝和浅拷贝的概念、区别、使用场景及具体实现方法。
一、深拷贝和浅拷贝的基本概念
1.1 深拷贝(Deep Copy)
深拷贝是指创建一个新对象,并递归地复制原对象中的所有数据(包括子对象),使得新对象与原对象在内存中是完全独立的。修改新对象不会影响原对象,反之亦然。深拷贝保证了对象的完全独立性和数据的完整性。
1.2 浅拷贝(Shallow Copy)
浅拷贝只是复制了对象本身,而没有复制其所引用的子对象。换句话说,浅拷贝仅仅复制了对象的引用(或指针),使得原对象和新对象共享同一块内存空间。因此,修改新对象中的引用类型数据会影响到原对象,反之亦然。
二、深拷贝和浅拷贝的区别
2.1 数据复制
- 深拷贝:复制所有数据,包括值和子对象。
- 浅拷贝:只复制对象的引用,不复制子对象。
2.2 对象关联
- 深拷贝:新对象与原对象完全独立,修改其中一个不会影响另一个。
- 浅拷贝:新对象与原对象共享部分数据(如子对象),修改其中一个可能会影响另一个。
2.3 内存占用
- 深拷贝:由于复制了所有数据,因此可能会占用更多内存。
- 浅拷贝:只复制引用,占用内存较少。
三、Go语言中深拷贝和浅拷贝的使用场景
3.1 深拷贝的使用场景
- 当需要创建一个独立的对象,并且不希望修改原始对象时,应使用深拷贝。例如,处理敏感数据时,为了避免数据泄露,需要复制一份新的数据进行操作。
- 在并发编程中,当多个goroutine需要操作同一份数据的副本时,为了防止竞态条件,应使用深拷贝。
3.2 浅拷贝的使用场景
- 当需要创建一个对象的副本,并且希望修改副本时,可以使用浅拷贝。这样,修改副本的同时也会影响到原对象,这在某些场景下是需要的。
- 在某些性能敏感的场景下,为了减少内存占用和提高性能,可以使用浅拷贝。
四、Go语言中深拷贝和浅拷贝的实现方法
4.1 浅拷贝的实现
在Go语言中,值类型的变量在赋值时默认进行深拷贝(因为值类型的数据直接存储在变量中),而引用类型的变量在赋值时默认进行浅拷贝(因为引用类型的数据存储在堆上,变量中只存储指向数据的指针)。
示例1:引用类型的浅拷贝
package mainimport "fmt"func main() {slice1 := []int{1, 2, 3}slice2 := slice1 // 浅拷贝slice2[0] = 10fmt.Println(slice1) // 输出: [10 2 3]
}
在上面的例子中,slice2
是slice1
的浅拷贝,它们共享同一个底层数组。因此,修改slice2
的第一个元素也会影响到slice1
。
示例2:结构体的浅拷贝
package mainimport "fmt"type Dog struct {Name stringAge int
}func main() {dog1 := Dog{Name: "dog1", Age: 11}dog2 := dog1 // 浅拷贝dog2.Name = "dog2"fmt.Println(dog1) // 输出: {dog1 11}fmt.Println(dog2) // 输出: {dog2 11}
}
虽然在这个例子中dog2
看起来像是dog1
的深拷贝(因为修改dog2.Name
没有影响到dog1.Name
),但实际上这是因为Dog
结构体中的字段都是值类型,它们在赋值时自然进行了深拷贝。如果Dog
结构体中包含引用类型的字段,那么就会表现出浅拷贝的特性。
4.2 深拷贝的实现
Go标准库中没有直接提供深拷贝的函数,但可以通过以下几种方式实现深拷贝:
4.2.1 使用json.Marshal
和json.Unmarshal
这是一种简单但效率较低的方法,适用于简单的结构体。通过将对象序列化为JSON字符串,然后再反序列化回一个新对象,从而实现深拷贝。
package mainimport ("encoding/json""fmt"
)type Person struct {Name stringAge int
}func main() {person1 := Person{"Alice", 25}var person2 Person// 序列化data, err := json.Marshal(person1)if err != nil {panic(err)}// 反序列化err = json.Unmarshal(data, &person2)if err != nil {panic(err)}person2.Age = 30fmt.Println(person1) // 输出: {Alice 25}fmt.Println(person2) // 输出: {Alice 30}
}
这种方法虽然简单,但有几个缺点:
- 需要对象可以序列化为JSON格式,且序列化和反序列化过程需要遍历整个对象,效率较低。
- 如果对象中包含循环引用或特殊类型(如函数、channel等),则无法正确序列化。
4.2.2 递归深拷贝
对于复杂的数据结构,可以使用递归深拷贝的方法。这种方法需要手动编写代码来遍历对象中的所有字段,如果是基本类型则直接复制,如果是复杂类型(如结构体、切片、映射等)则递归调用深拷贝函数。
package mainimport ("fmt""reflect"
)func DeepCopy(input interface{}) interface{} {if input == nil {return nil}switch reflect.TypeOf(input).Kind() {case reflect.Bool, reflect.String, reflect.Int, reflect.Int8, reflect.Int16,reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64:return inputcase reflect.Struct:in := reflect.ValueOf(input)out := reflect.New(in.Type()).Elem()for i := 0; i < in.NumField(); i++ {field := in.Field(i)if field.CanInterface() {// 递归调用DeepCopyout.Field(i).Set(reflect.ValueOf(DeepCopy(field.Interface())))}}return out.Interface()// 可以根据需要添加对切片、映射等类型的支持default:// 其他类型根据需要进行处理return input}
}func main() {// 示例结构体type Object struct {Num intStr stringSlice []intMap map[string]intPerson}// 匿名结构体Persontype Person struct {Name stringAge int}obj1 := &Object{Num: 1,Str: "hello",Slice: []int{2, 3},Map: map[string]int{"age": 18},Person: Person{Name: "Lucas",Age: 20,},}// 深拷贝obj2 := DeepCopy(obj1).(*Object)// 修改obj1的Name字段obj1.Person.Name = "Nina"fmt.Println("obj1:", obj1)fmt.Println("obj2:", obj2)
}// 输出结果:
// obj1: &{1 hello [2 3] map[age:18] {Nina 20}}
// obj2: &{1 hello [2 3] map[age:18] {Lucas 20}}
在这个例子中,我们定义了一个DeepCopy
函数,它使用反射来遍历对象中的所有字段,并根据字段类型进行相应的深拷贝处理。对于结构体类型,我们递归调用DeepCopy
函数来处理其内部字段。这样,无论对象包含多少层嵌套,都能实现完整的深拷贝。
五、总结
深拷贝和浅拷贝是Go语言中常见的对象复制方式,它们的主要区别在于是否真正获取到了被拷贝对象的单独掌控权。深拷贝会复制对象的所有数据和子对象,使得新对象与原对象完全独立;而浅拷贝只是复制了对象的引用,使得新对象与原对象共享同一块内存空间。在选择使用深拷贝还是浅拷贝时,需要根据具体的场景和需求来决定。
在实际编程中,深拷贝通常比浅拷贝更加复杂和昂贵,因为它需要递归地复制对象的所有部分,包括嵌套的对象和集合。然而,深拷贝提供了更高的数据独立性和安全性,避免了因修改副本而影响原始数据的风险。