golang对象深拷贝之使用gob的坑

先参考之前写的文章 golang-对象深拷贝的常见方式及性能

本文仅介绍使用gob时遇见的坑,熟悉go的都知道,若结构体以小写字母开头,则表示该字段不希望外部读到,相当于Javaprivate。本次遇见的坑便是在此基础上产生的

使用gob针对对象进行深拷贝,代码如下

go 复制代码
func copy(dst, src interface{}) error {
   var buf bytes.Buffer
   if err := gob.NewEncoder(&buf).Encode(src); err != nil {
      return err
   }
   return gob.NewDecoder(bytes.NewBuffer(buf.Bytes())).Decode(dst)
}

正常的case

go 复制代码
func TestCopy(t *testing.T) {
   type NormalData struct {
      Name   string
      Gender int32 //0:woman 1:man 2:other
   }

   normalDataSrc := &NormalData{
      Name:   "This is whoops",
      Gender: int32(1),
   }

   normalDataDst := &NormalData{}
   copy(normalDataDst, normalDataSrc)
   normalDataDst.Name = "this is a clone"

   fmt.Println(normalDataSrc, normalDataDst)
}

上述代码输出以下内容:

shell 复制代码
&{This is whoops 1} &{this is a clone 1}

可以发现copy无问题。

错误的case

golang 复制代码
func TestErrorCopy(t *testing.T) {
   type NormalData struct {
      Name   string
      Gender int32 //0:woman 1:man 2:other
      Lock   *sync.Mutex
   }

   normalDataSrc := &NormalData{
      Name:   "This is whoops",
      Gender: int32(1),
      Lock:   &sync.Mutex{},
   }

   normalDataDst := &NormalData{}
   if err := copy(normalDataDst, normalDataSrc); err != nil {
      fmt.Printf("copy err %s\n", err.Error())
      return
   } else {
      normalDataDst.Name = "this is a clone"
      fmt.Println(normalDataSrc, normalDataDst)
   }

}

上述代码输出以下内容:

shell 复制代码
copy err gob: type sync.Mutex has no exported fields

考虑下这是为什么呢?明明Lock是大写的是可以被导出的,为什么报错了?

根据错误内容分析下,可以发现主要发生在 sync.Mutex 这个类型,查看其源码

go 复制代码
type Mutex struct {
   state int32
   sema  uint32
}

发现其字段均不对外开放,因此导致 gobEncode时失败了,具体代码在 gob/encode.go[go1.16]647 行,可以自行debug进行调试。

那么怎么解决这种错误呢?参考下述代码

gob深拷贝结合自定义copy

实现起来就是field全为大写的可以通过gob这种深拷贝,存在field为小写的采用自定义复制方式。

go 复制代码
func TestNoExportedCopy(t *testing.T) {
   type OuterData struct {
      Name   string
      Gender int32 //0:woman 1:man 2:other
   }

   type InnerData struct {
      Lock *sync.Mutex
   }
   type NormalData struct {
      //可copy部分,即无小写字母开头的field
      *OuterData
      //不可copy部分,即存在小写字母开头的field
      *InnerData
   }

   normalDataSrc := &NormalData{
      OuterData: &OuterData{
         Name:   "This is whoops",
         Gender: int32(1)},
      InnerData: &InnerData{
         Lock: &sync.Mutex{},
      },
   }

   normalOuterDataDst := &OuterData{}
   if err := copy(normalOuterDataDst, normalDataSrc.OuterData); err != nil {
      fmt.Printf("copy err %s\n", err.Error())
      return
   } else {
      normalDataDst := &NormalData{OuterData: normalOuterDataDst}
      normalDataDst.OuterData.Name = "this is a clone"
      normalDataDst.InnerData = normalDataSrc.InnerData
      fmt.Println("原结构体:", normalDataSrc.OuterData, &normalDataSrc.Lock)
      fmt.Println("copy结构体:", normalDataDst.OuterData, &normalDataDst.Lock)
   }
}

上述代码输出以下内容:

shell 复制代码
原结构体: &{This is whoops 1} 0xc000010a80
copy结构体: &{this is a clone 1} 0xc000010a80

综上需要注意在使用gob时虽然代码相对简洁,但是若结构体存在小写的field,则会导致 type xxxxx has no exported fields。若想解决这点,在必须存在小写filed时,需考虑将字段通过xxxOuter,xxxInner的方式将结构体进行拆分,并结合自定义copy方式来实现。

注: 个人还是比较建议优先多动手去一一赋值,其次使用json序列化与反序列化方式,最后再采用gob这种方式。

Tips: 上述 Lock字段实际上并未发生copy,只是指向了原有指针地址。

相关推荐
Java陈序员3 小时前
代码检测器!一款专门揭露屎山代码的质量分析工具!
docker·go
豆浆Whisky4 小时前
Go编译器优化秘籍:性能提升的黄金参数详解|Go语言进阶(16)
后端·go
不爱笑的良田4 小时前
从零开始的云原生之旅(九):云原生的核心优势:自动弹性伸缩实战
云原生·容器·kubernetes·go
无限中终1 天前
ENERGY Designer:重构跨平台GUI开发的高效解决方案
重构·go·结对编程
shining2 天前
[Golang] 万字详解,深入剖析context
go
一语长情2 天前
Go高并发背后的功臣:Goroutine调度器详解
后端·架构·go
代码扳手2 天前
Go 开发的“热更新”真相:从 fresh 到真正的零停机思考
后端·go
cr7xin3 天前
缓存查询逻辑及问题解决
数据库·redis·后端·缓存·go
ljq3 天前
Go:interface原理详解-接口由使用者定义,而不是由实现者定义。接口的常见疑惑
go
半枫荷3 天前
十二、Go语法进阶(接口和泛型)
go