概念
各编程语言都有深浅拷贝的概念, 它们的核心是拷贝的是地址
(浅拷贝)还是值
(深拷贝), 今天我们就对其概念介绍, 并做性能对比和特性对比
浅拷贝
- 拷贝的只是数据的
地址
. 新老对象共享内存 - 当改新对象时, 老对象也会被改变. 当释放新对象时, 老对象也会被释放.
- 浅拷贝的数据类型, 即引用类型数据: 有
Slice(无论原生类型或结构体slice), Map
深拷贝
- 深拷贝的是数据的
内容
, 会创建新对象, 新老对象不共享内存 - 当改变新对象时, 和老对象没关系; 当释放新对象时, 和老对象也没关系; 总之就是新老对象没任何关系
- 深拷贝的数据类型, 即值类型数据: 有
Struct, Array, Int, String, Float, Bool
用途
当我们需要拷贝一个新对象时, 怎么选择呢? 显然
- 当需要节省内存时, 可选浅拷贝, 因为只是复制了地址
- 当为了操作简便时, 可选浅拷贝, 直接
a := b
即可 - 而当需要开辟新内存, 分别处理新老对象, 使两者互不干扰时, 则需要深拷贝
- 例如数据流业务的多条支流间, 若均存在改写对象的操作, 则需深拷贝, 防止A逻辑分支改了a对象, 而后续B逻辑分支意外的使用了被别人改过的对象
性能对比
有Reflect, GobEncode, JsonMarshal几种方式, 我们做一个Benchmark试验如下
- 定义数据类型
type InnerStruct struct {
Int int
String string
}
type CType struct {
Int int
String string
Struct InnerStruct
IntSlice []int
StringSlice []string
StructSlice []InnerStruct
IntArray [2]int
StringArray [2]string
StructArray [2]InnerStruct
Map map[string]string
}
- 初始化数据
c := &CType{
Int: 1,
String: "2",
Struct: InnerStruct{Int: 3, String: "4"},
IntSlice: []int{5, 6},
StringSlice: []string{"7", "8"},
StructSlice: []InnerStruct{{Int: 9, String: "10"}, {Int: 11, String: "12"}},
IntArray: [2]int{13, 14},
StringArray: [2]string{"15", "16"},
StructArray: [2]InnerStruct{{Int: 17, String: "18"}, {Int: 19, String: "20"}},
Map: map[string]string{"21": "22", "23": "24"},
}
测试电脑为cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
浅拷贝
func BenchmarkShallowCopy(b *testing.B) {
for n := 0; n < b.N; n++ {
c.ShallowCopy()
}
}
结果为BenchmarkShallowCopy 0.7702 ns/op
Reflect
func BenchmarkReflect(b *testing.B) {
for n := 0; n < b.N; n++ {
c.DeepCopyReflect()
}
}
结果为BenchmarkReflect 4515 ns/op
JsonMarshal
func BenchmarkJsonMarshal(b *testing.B) {
for n := 0; n < b.N; n++ {
c.DeepCopyJsonMarshal()
}
}
结果为BenchmarkJsonMarshal 11133 ns/op
GobEncode
func BenchmarkGobMarshal(b *testing.B) {
for n := 0; n < b.N; n++ {
c.DeepCopyGobMarshal()
}
}
结果为BenchmarkGobMarshal 54298 ns/op
综上, 浅拷贝
性能>Reflect
性能>JsonMarshal
性能>GobMarshal
特点对比
做完性能对别后, 我们还需对别深浅拷贝是否对新对象的写操作有影响
同样用上文性能对比
的数据, 分别测试对拷贝后新对象的写操作, 是否影响老对象, 结果如下
浅拷贝
func TestShallowCopy(t *testing.T) {
r := c.ShallowCopy()
r.Int += 10000
r.String += "changed"
r.Struct.Int += 10000
r.Struct.String += "changed"
r.IntSlice[0] += 10000
r.IntSlice[1] += 10000
r.StringSlice[0] += "changed"
r.StringSlice[1] += "changed"
r.StructSlice[0].Int += 10000
r.StructSlice[0].String += "changed"
r.StructSlice[1].Int += 10000
r.StructSlice[1].String += "changed"
r.IntArray[0] += 10000
r.IntArray[1] += 10000
r.StringArray[0] += "changed"
r.StringArray[1] += "changed"
r.StructArray[0].Int += 10000
r.StructArray[0].String += "changed"
r.StructArray[1].Int += 10000
r.StructArray[1].String += "changed"
r.Map["21"] += "changed"
r.Map["23"] += "changed"
rByte, _ := json.MarshalIndent(r, "", " ")
t.Log(string(rByte))
}
结果如下可见, 确实只有Slice(无论原生类型或结构体slice), Map
有影响, 其他类型均无影响
=== RUN TestShallowCopy
main_test.go:117: {
"Int": 1,
"String": "2",
"Struct": {
"Int": 3,
"String": "4"
},
"IntSlice": [
10005,
10006
],
"StringSlice": [
"7changed",
"8changed"
],
"StructSlice": [
{
"Int": 10009,
"String": "10changed"
},
{
"Int": 10011,
"String": "12changed"
}
],
"IntArray": [
13,
14
],
"StringArray": [
"15",
"16"
],
"StructArray": [
{
"Int": 17,
"String": "18"
},
{
"Int": 19,
"String": "20"
}
],
"Map": {
"21": "22changed",
"23": "24changed"
}
}
Reflect
func TestDeepCopyReflect(t *testing.T) {
r := c.DeepCopyReflect()
r.Int += 10000
r.String += "changed"
r.Struct.Int += 10000
r.Struct.String += "changed"
r.IntSlice[0] += 10000
r.IntSlice[1] += 10000
r.StringSlice[0] += "changed"
r.StringSlice[1] += "changed"
r.StructSlice[0].Int += 10000
r.StructSlice[0].String += "changed"
r.StructSlice[1].Int += 10000
r.StructSlice[1].String += "changed"
r.IntArray[0] += 10000
r.IntArray[1] += 10000
r.StringArray[0] += "changed"
r.StringArray[1] += "changed"
r.StructArray[0].Int += 10000
r.StructArray[0].String += "changed"
r.StructArray[1].Int += 10000
r.StructArray[1].String += "changed"
r.Map["21"] += "changed"
r.Map["23"] += "changed"
rByte, _ := json.MarshalIndent(c, "", " ")
t.Log(string(rByte))
}
结果如下, 确实新对象的任何改动, 都没影响老对象
=== RUN TestDeepCopyReflect
main_test.go:117: {
"Int": 1,
"String": "2",
"Struct": {
"Int": 3,
"String": "4"
},
"IntSlice": [
5,
6
],
"StringSlice": [
"7",
"8"
],
"StructSlice": [
{
"Int": 9,
"String": "10"
},
{
"Int": 11,
"String": "12"
}
],
"IntArray": [
13,
14
],
"StringArray": [
"15",
"16"
],
"StructArray": [
{
"Int": 17,
"String": "18"
},
{
"Int": 19,
"String": "20"
}
],
"Map": {
"21": "22",
"23": "24"
}
}
JsonMarshal
func TestDeepCopyJsonMarshal(t *testing.T) {
r := c.DeepCopyJsonMarshal()
r.Int += 10000
r.String += "changed"
r.Struct.Int += 10000
r.Struct.String += "changed"
r.IntSlice[0] += 10000
r.IntSlice[1] += 10000
r.StringSlice[0] += "changed"
r.StringSlice[1] += "changed"
r.StructSlice[0].Int += 10000
r.StructSlice[0].String += "changed"
r.StructSlice[1].Int += 10000
r.StructSlice[1].String += "changed"
r.IntArray[0] += 10000
r.IntArray[1] += 10000
r.StringArray[0] += "changed"
r.StringArray[1] += "changed"
r.StructArray[0].Int += 10000
r.StructArray[0].String += "changed"
r.StructArray[1].Int += 10000
r.StructArray[1].String += "changed"
r.Map["21"] += "changed"
r.Map["23"] += "changed"
rByte, _ := json.MarshalIndent(c, "", " ")
t.Log(string(rByte))
}
结果如下, 确实新对象的任何改动, 都没影响老对象
=== RUN TestDeepCopyJsonMarshal
main_test.go:117: {
"Int": 1,
"String": "2",
"Struct": {
"Int": 3,
"String": "4"
},
"IntSlice": [
5,
6
],
"StringSlice": [
"7",
"8"
],
"StructSlice": [
{
"Int": 9,
"String": "10"
},
{
"Int": 11,
"String": "12"
}
],
"IntArray": [
13,
14
],
"StringArray": [
"15",
"16"
],
"StructArray": [
{
"Int": 17,
"String": "18"
},
{
"Int": 19,
"String": "20"
}
],
"Map": {
"21": "22",
"23": "24"
}
}
GobEncode
func TestDeepCopyGobMarshal(t *testing.T) {
r := c.DeepCopyGobMarshal()
r.Int += 10000
r.String += "changed"
r.Struct.Int += 10000
r.Struct.String += "changed"
r.IntSlice[0] += 10000
r.IntSlice[1] += 10000
r.StringSlice[0] += "changed"
r.StringSlice[1] += "changed"
r.StructSlice[0].Int += 10000
r.StructSlice[0].String += "changed"
r.StructSlice[1].Int += 10000
r.StructSlice[1].String += "changed"
r.IntArray[0] += 10000
r.IntArray[1] += 10000
r.StringArray[0] += "changed"
r.StringArray[1] += "changed"
r.StructArray[0].Int += 10000
r.StructArray[0].String += "changed"
r.StructArray[1].Int += 10000
r.StructArray[1].String += "changed"
r.Map["21"] += "changed"
r.Map["23"] += "changed"
rByte, _ := json.MarshalIndent(c, "", " ")
t.Log(string(rByte))
}
结果如下, 确实新对象的任何改动, 都没影响老对象
=== RUN TestDeepCopyGobMarshal
main_test.go:117: {
"Int": 1,
"String": "2",
"Struct": {
"Int": 3,
"String": "4"
},
"IntSlice": [
5,
6
],
"StringSlice": [
"7",
"8"
],
"StructSlice": [
{
"Int": 9,
"String": "10"
},
{
"Int": 11,
"String": "12"
}
],
"IntArray": [
13,
14
],
"StringArray": [
"15",
"16"
],
"StructArray": [
{
"Int": 17,
"String": "18"
},
{
"Int": 19,
"String": "20"
}
],
"Map": {
"21": "22",
"23": "24"
}
}