首页 Go深拷贝
文章
取消

Go深拷贝

在 Python 中,对对象的拷贝有 copy.copy()copy.deepcopy(),前者是浅拷贝,后者是深拷贝。如果想让拷贝出来的新对象与旧对象彻底没有关系我们就要使用深拷贝,深拷贝的对象的每个元素,每个 field 的内存地址都与原对象不同,但值相同。

在 Go 中没有提供深拷贝的原生调用,需要用的话需要自己来实现。

json序列化、反序列化

我们可以使用 json 序列化、反序列化来实现变量的深拷贝,实际是利用序列化后的 bytes 对对象进行重建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func DeepCopy(src, dest interface{}) error {
	if src == nil || dest == nil {
		return fmt.Errorf("nil src or dest")
	}
	bytes, err := json.Marshal(src)
	if err != nil {
		return fmt.Errorf("unable to serialize src:%s", err.Error())
	}
	err = json.Unmarshal(bytes, dest)
	if err != nil {
		return fmt.Errorf("unable to deserialize into dest:%s", err.Error())
	}
	return nil
}

测试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
type Foo struct {
	Name string
}

type Demo struct {
	A string
	b int64
	C float64
	D []string
	E map[string]string
	F *Foo
	G Foo
	H time.Time
	I interface{}
	J map[int]Foo
	K interface{}
	L []Foo
}

func main() {
	d1 := Demo{
		A: "a",
		b: 1,
		C: 2.3,
		D: []string{"b"},
		E: map[string]string{"c": "C", "d": "D"},
		F: &Foo{Name: "foo"},
		G: Foo{Name: "Popcorn"},
		H: time.Now(),
		I: map[int]Foo{1: {Name: "foo1"}},
		J: map[int]Foo{1: {Name: "foo2"}},
		K: []Foo,
		L: []Foo,
	}
	var d2 Demo
	if err := DeepCopy(d1, &d2); err != nil {
		fmt.Println(err)
	}
	fmt.Printf("d1: %#v \n", d1)
	fmt.Printf("d2: %#v \n", d2)

	var d3 int = 10
	var d4 int
	err := DeepCopy(d3, &d4)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("d3:%d \n", d3)
	fmt.Printf("d4:%d \n", d4)
}

输出:

1
2
3
4
d1: main.Demo{A:"a", b:1, C:2.3, D:[]string{"b"}, E:map[string]string{"c":"C", "d":"D"}, F:(*main.Foo)(0xc0000142a0), G:main.Foo{Name:"Popcorn"}, H:time.Date(2022, time.December, 14, 15, 48, 10, 972990000, time.Local), I:map[int]main.Foo{1:main.Foo{Name:"foo1"}}, J:map[int]main.Foo{1:main.Foo{Name:"foo2"}}, K:[]main.Foo{main.Foo{Name:"foo3"}}, L:[]main.Foo{main.Foo{Name:"foo4"}}} 
d2: main.Demo{A:"a", b:0, C:2.3, D:[]string{"b"}, E:map[string]string{"c":"C", "d":"D"}, F:(*main.Foo)(0xc0000146a0), G:main.Foo{Name:"Popcorn"}, H:time.Date(2022, time.December, 14, 15, 48, 10, 972990000, time.Local), I:map[string]interface {}{"1":map[string]interface {}{"Name":"foo1"}}, J:map[int]main.Foo{1:main.Foo{Name:"foo2"}}, K:[]interface {}{map[string]interface {}{"Name":"foo3"}}, L:[]main.Foo{main.Foo{Name:"foo4"}}} 
d3:10 
d4:10 

这种深拷贝方式缺点很明显:

  • 性能较差,对于对象拷贝来说成本较大
  • 对于结构体未导出的字段无法拷贝,如 d2.b 没有拷贝出来
  • 有的类型不能实现复制,比如函数,如果复制函数的话 json 序列化会抛出异常
  • 有的类型复制出来的类型会错,如 interface。d2.I,新复制出来的 map 值会为 map[string]interface{} 类型;d2.K,新复制出来的 slice 值会为 []interface{} 类型

json 序列化、反序列化本质上使用了反射,反序列化的开销比序列化大的多。下面介绍下用反射实现深拷贝。

反射

反射提供了一种程序运行时访问、检测和修改变量本身状态的一种能力。可以通过反射实现深拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// DeepCopy 深拷贝
func DeepCopy(src interface{}) interface{} {
	if src == nil {
		return nil
	}
	ori := reflect.ValueOf(src)
	cpy := reflect.New(ori.Type()).Elem()

	recursiveCopy(ori, cpy)
	return cpy.Interface()
}

// 递归拷贝
func recursiveCopy(ori, cpy reflect.Value) {
	switch ori.Kind() {
	case reflect.Ptr:
		// 需要复制的是指针,其实并不是复制指针本身,而是指针所指向的对象,递归复制
		oriVal := ori.Elem()
		if !oriVal.IsValid() {
			return
		}
		cpy.Set(reflect.New(oriVal.Type()))
		recursiveCopy(oriVal, cpy.Elem())
	case reflect.Interface:
		// 需要复制 interface{},需要现对接口字段包含的值反射,再递归复制
		if ori.IsNil() {
			// 为nil,不用复制
			return
		}
		oriVal := ori.Elem()
		// 反射接口的值
		cpyVal := reflect.New(oriVal.Type()).Elem()
		recursiveCopy(oriVal, cpyVal)
		cpy.Set(cpyVal)
	case reflect.Struct:
		// 需要复制结构体,需要对每个 field 复制
		t, ok := ori.Interface().(time.Time)
		if ok {
			// time.Time 比较特殊,可以直接通过复制值
			cpy.Set(reflect.ValueOf(t))
			return
		}
		for i := 0; i < ori.NumField(); i++ {
			// 遍历 field 复制
			if ori.Type().Field(i).PkgPath != "" {
				// 跳过未导出的字段
				continue
			}
			recursiveCopy(ori.Field(i), cpy.Field(i))
		}
	case reflect.Slice:
		// 需要复制 slice,复制所有元素
		if ori.IsNil() {
			// 为nil,不用复制
			return
		}
		// 对 cpy 设置同 ori 相同的元素类型、长度与容量
		cpy.Set(reflect.MakeSlice(ori.Type(), ori.Len(), ori.Cap()))
		for i := 0; i < ori.Len(); i++ {
			// 复制元素
			recursiveCopy(ori.Index(i), cpy.Index(i))
		}
	case reflect.Map:
		// 需要复制 map,对 key、val 递归复制
		if ori.IsNil() {
			// 为nil,不用复制
			return
		}
		cpy.Set(reflect.MakeMap(ori.Type()))
		for _, key := range ori.MapKeys() {
			// 取 val 值
			oriVal := ori.MapIndex(key)
			// 创建 val 同类型的cpy 对象
			cpyVal := reflect.New(oriVal.Type()).Elem()
			// 将 oriVal 递归复制 给 cpyVal
			recursiveCopy(oriVal, cpyVal)
			// 复制 key
			cpyKey := DeepCopy(key.Interface())
			//  对 cpy 赋值 key、value
			cpy.SetMapIndex(reflect.ValueOf(cpyKey), cpyVal)
		}
	default:
		// 其他类型,即基础类型,可直接复制
		cpy.Set(ori)
	}
}

测试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
type Foo struct {
	Name string
}

type Demo struct {
	A string
	b int64
	C float64
	D []string
	E map[string]string
	F *Foo
	G Foo
	H time.Time
	I interface{}
	J map[int]Foo
	K interface{}
	L []Foo
}

func main() {
	d1 := Demo{
		A: "a",
		b: 1,
		C: 2.3,
		D: []string{"b"},
		E: map[string]string{"c": "C", "d": "D"},
		F: &Foo{Name: "foo"},
		G: Foo{Name: "Popcorn"},
		H: time.Now(),
		I: map[int]Foo{1: {Name: "foo1"}},
		J: map[int]Foo{1: {Name: "foo2"}},
		K: []Foo,
		L: []Foo,
	}
	d2 := DeepCopy(d1)
	fmt.Printf("d1: %#v \n", d1)
	fmt.Printf("d2: %#v \n", d2)

	var d3 int = 10
	d4 := DeepCopy(d3)
	fmt.Printf("d3:%d \n", d3)
	fmt.Printf("d4:%d \n", d4)
}

输出:

1
2
3
4
d1: main.Demo{A:"a", b:1, C:2.3, D:[]string{"b"}, E:map[string]string{"c":"C", "d":"D"}, F:(*main.Foo)(0xc000014260), G:main.Foo{Name:"Popcorn"}, H:time.Date(2022, time.December, 13, 15, 55, 11, 778019000, time.Local), I:map[int]main.Foo{1:main.Foo{Name:"foo1"}}, J:map[int]main.Foo{1:main.Foo{Name:"foo2"}}, K:[]main.Foo{main.Foo{Name:"foo3"}}, L:[]main.Foo{main.Foo{Name:"foo4"}}} 
d2: main.Demo{A:"a", b:0, C:2.3, D:[]string{"b"}, E:map[string]string{"c":"C", "d":"D"}, F:(*main.Foo)(0xc000014340), G:main.Foo{Name:"Popcorn"}, H:time.Date(2022, time.December, 13, 15, 55, 11, 778019000, time.Local), I:map[int]main.Foo{1:main.Foo{Name:"foo1"}}, J:map[int]main.Foo{1:main.Foo{Name:"foo2"}}, K:[]main.Foo{main.Foo{Name:"foo3"}}, L:[]main.Foo{main.Foo{Name:"foo4"}}} 
d3:10 
d4:10 

从这个输出结果来看,我们可以看到相比于用 json序列化、反序列化的方法,使用反射可以解决前者的缺点,但不能复制未导出字段。

如果将下面这段 代码注释掉的话,

1
2
3
4
if ori.Type().Field(i).PkgPath != "" {
    // 跳过未导出的字段
    continue
}

会引发 panic

1
panic: reflect: reflect.Value.Set using value obtained using unexported field
本文由作者按照 CC BY 4.0 进行授权