golang中的深度复制数据结构
Deep copying data structures in golang
我想复制一个数据结构的实例。由于 go 没有任何内置函数,我使用的是第三方库:https://github.com/emirpasic/gods
。
例如,我可能会尝试使用带有哈希集的深拷贝。
var c, d hashset.Set
c = *hashset.New()
c.Add(1)
deepcopy.Copy(d, c)
c.Add(2)
fmt.Println(c.Contains(2))
fmt.Println(d.Contains(2))
fmt.Println(c.Contains(1))
fmt.Println(d.Contains(1))
然而,哈希集的内容根本没有被复制。我知道深度复制模块无法复制未导出的值,但由于库中没有内置 "copy constructor",这是否意味着在不修改其代码的情况下无法完全复制库中的数据结构实例? (类似的问题发生在我调查过的其他一些库中)。
我是golang新手,感觉不太对,因为类似的事情可以很容易地在C++中实现。我知道我可以编写自己的版本或修改他们的代码,但这比预期的工作量大,这就是为什么我认为应该有一种惯用的方法。
PS:对于可能会说"there is no need of such functionality"的人,我是把一些复杂的状态和一些数据结构分配给并行计算线程,他们直接使用状态,不能相互干扰。
如果您使用的包没有提供数据结构的复制功能,那么我会自己编写type-safe复制功能。此外,我不会担心未导出的字段,因为开发人员决定向用户隐藏这些字段一定是有原因的。
在你的例子中:
func NewHashSetCopy(src *hashset.Set) *hashset.Set{
dst := *hashset.New()
iterator := src.Iter()
for elem := range iterator {
dst.Add(elem)
}
return dst
}
不幸与否,在 Go 中没有办法做到这一点。第一个想到的工具是反射(包reflect
), but using reflection you can only read unexported fields, but you can't set them. See
克隆具有未导出字段的结构的唯一方法是使用包 unsafe
(see an example here: Access unexported fields in golang/reflect?),但正如它的名字所说:它 不安全 ,你应该远离它尽可能。使用 unsafe
创建的程序不能保证它们可以继续使用较新的 Go 版本,或者它们在每个平台上的行为都相同。
通常在 Go 中支持克隆的唯一正确方法是包本身是否支持此类操作。
注意#1:
这并不意味着在某些特定情况下您不能"mimic"通过创建新值并手动构建其状态来进行克隆。例如,您可以通过创建新地图、迭代 key-value 对原始地图并将它们设置在新地图中来克隆 map
。
注释 #2:
请注意,您可以通过简单地 assigning 将具有未导出字段的结构复制 "exact" 到另一个结构变量(相同类型),这也将正确复制未导出字段。
就像这个例子:
type person struct {
Name string
age *int
}
age := 22
p := &person{"Bob", &age}
fmt.Println(p)
p2 := new(person)
*p2 = *p
fmt.Println(p2)
哪个会输出(在Go Playground上试试):
&{Bob 0x414020}
&{Bob 0x414020}
我们甚至可以在不依赖具体类型的情况下使用 reflect
进行概括:
type person struct {
Name string
age *int
}
age := 22
p := &person{"Bob", &age}
fmt.Println(p)
v := reflect.ValueOf(p).Elem()
vp2 := reflect.New(v.Type())
vp2.Elem().Set(v)
fmt.Println(vp2)
在 Go Playground 上试试这个。
但是我们不能做的 是将person.age
未导出的字段更改为指向其他内容。没有声明包的帮助,它只能是nil
或相同的指针值(指向对象作为原始字段)。
另见相关:
如果需要深度复制 protobuf 结构,可以在 https://github.com/golang/protobuf
中使用 proto.Clone
,请参阅 godoc 以获得更多帮助。
如果您的结构是可序列化的,您可以将其转换为 JSON 并返回,这对我的用例来说已经足够了。
func CloneMyStruct(orig *model.MyStruct) (*model.MyStruct, error) {
origJSON, err := json.Marshal(orig)
if err != nil {
return nil, err
}
clone := model.MyStruct{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return nil, err
}
return &clone, nil
}
我想复制一个数据结构的实例。由于 go 没有任何内置函数,我使用的是第三方库:https://github.com/emirpasic/gods
。
例如,我可能会尝试使用带有哈希集的深拷贝。
var c, d hashset.Set
c = *hashset.New()
c.Add(1)
deepcopy.Copy(d, c)
c.Add(2)
fmt.Println(c.Contains(2))
fmt.Println(d.Contains(2))
fmt.Println(c.Contains(1))
fmt.Println(d.Contains(1))
然而,哈希集的内容根本没有被复制。我知道深度复制模块无法复制未导出的值,但由于库中没有内置 "copy constructor",这是否意味着在不修改其代码的情况下无法完全复制库中的数据结构实例? (类似的问题发生在我调查过的其他一些库中)。
我是golang新手,感觉不太对,因为类似的事情可以很容易地在C++中实现。我知道我可以编写自己的版本或修改他们的代码,但这比预期的工作量大,这就是为什么我认为应该有一种惯用的方法。
PS:对于可能会说"there is no need of such functionality"的人,我是把一些复杂的状态和一些数据结构分配给并行计算线程,他们直接使用状态,不能相互干扰。
如果您使用的包没有提供数据结构的复制功能,那么我会自己编写type-safe复制功能。此外,我不会担心未导出的字段,因为开发人员决定向用户隐藏这些字段一定是有原因的。
在你的例子中:
func NewHashSetCopy(src *hashset.Set) *hashset.Set{
dst := *hashset.New()
iterator := src.Iter()
for elem := range iterator {
dst.Add(elem)
}
return dst
}
不幸与否,在 Go 中没有办法做到这一点。第一个想到的工具是反射(包reflect
), but using reflection you can only read unexported fields, but you can't set them. See
克隆具有未导出字段的结构的唯一方法是使用包 unsafe
(see an example here: Access unexported fields in golang/reflect?),但正如它的名字所说:它 不安全 ,你应该远离它尽可能。使用 unsafe
创建的程序不能保证它们可以继续使用较新的 Go 版本,或者它们在每个平台上的行为都相同。
通常在 Go 中支持克隆的唯一正确方法是包本身是否支持此类操作。
注意#1:
这并不意味着在某些特定情况下您不能"mimic"通过创建新值并手动构建其状态来进行克隆。例如,您可以通过创建新地图、迭代 key-value 对原始地图并将它们设置在新地图中来克隆 map
。
注释 #2:
请注意,您可以通过简单地 assigning 将具有未导出字段的结构复制 "exact" 到另一个结构变量(相同类型),这也将正确复制未导出字段。
就像这个例子:
type person struct {
Name string
age *int
}
age := 22
p := &person{"Bob", &age}
fmt.Println(p)
p2 := new(person)
*p2 = *p
fmt.Println(p2)
哪个会输出(在Go Playground上试试):
&{Bob 0x414020}
&{Bob 0x414020}
我们甚至可以在不依赖具体类型的情况下使用 reflect
进行概括:
type person struct {
Name string
age *int
}
age := 22
p := &person{"Bob", &age}
fmt.Println(p)
v := reflect.ValueOf(p).Elem()
vp2 := reflect.New(v.Type())
vp2.Elem().Set(v)
fmt.Println(vp2)
在 Go Playground 上试试这个。
但是我们不能做的 是将person.age
未导出的字段更改为指向其他内容。没有声明包的帮助,它只能是nil
或相同的指针值(指向对象作为原始字段)。
另见相关:
如果需要深度复制 protobuf 结构,可以在 https://github.com/golang/protobuf
中使用 proto.Clone
,请参阅 godoc 以获得更多帮助。
如果您的结构是可序列化的,您可以将其转换为 JSON 并返回,这对我的用例来说已经足够了。
func CloneMyStruct(orig *model.MyStruct) (*model.MyStruct, error) {
origJSON, err := json.Marshal(orig)
if err != nil {
return nil, err
}
clone := model.MyStruct{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return nil, err
}
return &clone, nil
}