likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 6

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

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

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

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)
}

上述代码输出以下内容:

&{This is whoops 1} &{this is a clone 1}

可以发现copy无问题。

错误的case

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)
   }

}

上述代码输出以下内容:

copy err gob: type sync.Mutex has no exported fields

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

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

type Mutex struct {
   state int32
   sema  uint32
}

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

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

gob深拷贝结合自定义copy

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

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)
   }
}

上述代码输出以下内容:

原结构体: &{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,只是指向了原有指针地址。

转载自:https://juejin.cn/post/7343873659025244171
评论
请登录