GO-深拷贝

复习

在 Go 语言中,所有的函数参数传递都是值传递(pass by value),当将参数传递给函数时,实际上是将参数的副本传递给函数。

然而,这并不意味着在函数内部对参数的修改都不会影响原始数据。因为在 Go 中,有些数据类型本身就是引用类型,比如切片(slice)、映射(map)、通道(channel)、接口(interface)和指针(pointer)。当这些类型作为参数传递给函数时,虽然传递的是值,但值本身就是一个引用

代码陷阱

切片是结构

  • 预期:BeforeAfter是一样的值,因为参数传递都是值传递
package main

import (
"fmt"
"strconv"
)

type Person struct {
Name string
Age int
Address []string
}

func changepeople(people []Person) {
people[1].Address[0] = "Change"
}

func main() {
peoples := make([]Person, 0)
for i := 0; i < 10; i++ {
peoples = append(peoples, Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: []string{"address" + strconv.Itoa(i), strconv.Itoa(i)},
})
}
fmt.Println("Before", peoples)
changepeople(peoples)
fmt.Println("After", peoples)
}

结果

  • 我们原本是希望前后的peoples是一样的值,但是事实上,在fixpeople改变的值,也同步到了After
    • 这显然和预期不一样,我们是不是想到, 因为结构体是引用对象,所以结构体,是同步的

切片是基础类型

预期

  • 预期还是希望,BeforeAfter是一样的值
package main

import (
"fmt"
)

func changeint(ints []int) {
ints[0] = 100
}

func main() {
ints := make([]int, 0)
for i := 0; i < 10; i++ {
ints = append(ints, i)
}
fmt.Println("Before", ints)
changeint(ints)
fmt.Println("After", ints)
}

结果

  • 但是事实上,我们依旧获得了不同的值

如何解决

切片是基础类型

package main

import "fmt"

func changeint(ints []int) {
ints[0] = 100
}

func main() {
ints := make([]int, 0)
for i := 0; i < 10; i++ {
ints = append(ints, i)
}
fmt.Println("Before", ints)
intsCopy := make([]int, len(ints))
copy(intsCopy, ints)
changeint(intsCopy)
fmt.Println("Before", ints)
}

切片是结构体

package main

import (
"fmt"
"strconv"
)

type Person struct {
Name string
Age int
Address string
}

func changepeople(people []Person) {
people[1].Address = "Change"
}

func main() {
peoples := make([]Person, 0)
for i := 0; i < 10; i++ {
peoples = append(peoples, Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: "address",
})
}
fmt.Println("Before", peoples[1], "Addr", peoples)
peoplesCopy := make([]Person, len(peoples))
copy(peoplesCopy, peoples)
changepeople(peoplesCopy)
fmt.Println("After", peoples[1], "Addr", peoples)
}

切片是结构体:特殊情况。COPY()失效了?

期望输出一样

package main

import (
"fmt"
"strconv"
)

type Person struct {
Name string
Age int
Address []string
}

func changepeople(people []Person) {
people[1].Address[0] = "Change"
}

func main() {
peoples := make([]Person, 0)
for i := 0; i < 10; i++ {
peoples = append(peoples, Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: []string{"\"address\",\"" + strconv.Itoa(i) + "\""},
})
}
fmt.Println("Before", peoples[1], "Addr", peoples)
peoplesCopy := make([]Person, len(peoples))
copy(peoplesCopy, peoples)
changepeople(peoplesCopy)
fmt.Println("After", peoples[1], "Addr", peoples)
}

结果

  • 当我的切片里面包含了其他引用对象的时候,好像没有实现真正的深拷贝

数组结构体

package main

import (
"fmt"
"strconv"
)

type Person struct {
Name string
Age int
Address string
}

func changepeople(people [10]Person) {
people[1].Address = "Change"
}

func main() {
var peoples [10]Person
for i := 0; i < 10; i++ {
peoples[i] = Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: "address",
}
}
fmt.Println("Before", peoples)
changepeople(peoples)
fmt.Println("After", peoples)
}

数组结构体指针

期望

package main

import (
"fmt"
"strconv"
)

type Person struct {
Name string
Age int
Address string
}

func changepeople(people [10]*Person) {
people[1].Address = "Change"
}

func main() {
var peoples [10]*Person
for i := 0; i < 10; i++ {
peoples[i] = &Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: "address",
}
}
fmt.Println("Before", peoples[1], "Addr", peoples)
changepeople(peoples)
fmt.Println("After", peoples[1], "Addr", peoples)
}

J结果

  • 在这情况下,我们修改了指针的数组的地址的值,所以在After之后,数组值变了

深拷贝与浅拷贝

  • 浅拷贝:只复制了数据结构的最外层,嵌套的数据结构仍然共享相同的内存地址。这意味着如果嵌套的数据结构被修改,所有指向它的副本都会受到影响。

  • 深拷贝:不仅复制了数据结构的最外层,还复制了嵌套的数据结构,使得副本和原始数据完全独立,互不影响

Go通过反射实现深拷贝

package utils

import (
"reflect"
"time"
)

// Interface for delegating copy process to type
type Interface interface {
DeepCopy() interface{}
}

// Iface is an alias to Copy; this exists for backwards compatibility reasons.
func Iface(iface interface{}) interface{} {
return Copy(iface)
}

// Copy creates a deep copy of whatever is passed to it and returns the copy
// in an interface{}. The returned value will need to be asserted to the
// correct type.
func Copy(src interface{}) interface{} {
if src == nil {
return nil
}

// Make the interface a reflect.Value
original := reflect.ValueOf(src)

// Make a copy of the same type as the original.
cpy := reflect.New(original.Type()).Elem()

// Recursively copy the original.
copyRecursive(original, cpy)

// Return the copy as an interface.
return cpy.Interface()
}

// copyRecursive does the actual copying of the interface. It currently has
// limited support for what it can handle. Add as needed.
func copyRecursive(original, cpy reflect.Value) {
// check for implement deepcopy.Interface
if original.CanInterface() {
if copier, ok := original.Interface().(Interface); ok {
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
return
}
}

// handle according to original's Kind
switch original.Kind() {
case reflect.Ptr:
// Get the actual value being pointed to.
originalValue := original.Elem()

// if it isn't valid, return.
if !originalValue.IsValid() {
return
}
cpy.Set(reflect.New(originalValue.Type()))
copyRecursive(originalValue, cpy.Elem())

case reflect.Interface:
// If this is a nil, don't do anything
if original.IsNil() {
return
}
// Get the value for the interface, not the pointer.
originalValue := original.Elem()

// Get the value by calling Elem().
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
cpy.Set(copyValue)

case reflect.Struct:
t, ok := original.Interface().(time.Time)
if ok {
cpy.Set(reflect.ValueOf(t))
return
}
// Go through each field of the struct and copy it.
for i := 0; i < original.NumField(); i++ {
// The Type's StructField for a given field is checked to see if StructField.PkgPath
// is set to determine if the field is exported or not because CanSet() returns false
// for settable fields. I'm not sure why. -mohae
if original.Type().Field(i).PkgPath != "" {
continue
}
copyRecursive(original.Field(i), cpy.Field(i))
}

case reflect.Slice:
if original.IsNil() {
return
}
// Make a new slice and copy each element.
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
for i := 0; i < original.Len(); i++ {
copyRecursive(original.Index(i), cpy.Index(i))
}

case reflect.Map:
if original.IsNil() {
return
}
cpy.Set(reflect.MakeMap(original.Type()))
for _, key := range original.MapKeys() {
originalValue := original.MapIndex(key)
copyValue := reflect.New(originalValue.Type()).Elem()
copyRecursive(originalValue, copyValue)
copyKey := Copy(key.Interface())
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
}

default:
cpy.Set(original)
}
}

Go深拷贝库

性能测试

测试结果

测试func

package utils

import (
"fmt"
"github.com/huandu/go-clone"
"strconv"
"sync"
"testing"
"time"
)

type Person struct {
Name string
Age int
Address []string
}

var wg sync.WaitGroup

func fixpeople(people []Person) {
people[1].Address[0] = "Change"
}
func TestCopy(t *testing.T) {
peoples := make([]Person, 0)
for i := 0; i < 1000000; i++ {
peoples = append(peoples, Person{
Name: "joo" + strconv.Itoa(i),
Age: i,
Address: []string{"address" + strconv.Itoa(i), strconv.Itoa(i)},
})
}
wg.Add(3)
go func([]Person) {
startDeepCOpy(peoples)
wg.Done()
}(peoples)
go func([]Person) {
startClone(peoples)
wg.Done()
}(peoples)
go func() {
startCloneSlow(peoples)
wg.Done()
}()

//t.Log(peoples)
wg.Wait()
}

func startCloneSlow(people []Person) []Person {
now := time.Now()
slowly := clone.Slowly(people).([]Person)
fixpeople(slowly)

fmt.Println("startCloneSlow Time", time.Since(now).Seconds())
return slowly
}
func startClone(people []Person) []Person {
now := time.Now()
peoples := clone.Clone(people).([]Person)
fixpeople(peoples)

fmt.Println("startClone Time", time.Since(now).Seconds())
return people
}

func startDeepCOpy(peoplesCopy []Person) []Person {
now := time.Now()

people := Copy(peoplesCopy).([]Person)
fixpeople(people)
fmt.Println("startDeepCopy Time", time.Since(now).Seconds())
return people
}