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

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

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

使用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
}

发现其字段均不对外开放,因此导致 gob在Encode时失败了,具体代码在 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,只是指向了原有指针地址。

相关推荐
大然Ryan5 小时前
我用 Go 和 AI 做了一个 macOS 磁盘清理工具:Vole(开源)
go
我不会起名字3227 小时前
seq 空洞排查:Mongo 的 51 只是预留上界,Redis CURR 才是已消耗边界
redis·后端·mongodb·go·即时通讯·im·故障排查
喵个咪1 天前
GoWind 拆解:全链 ID 类型体系——int64 定版与前端字符串化契约
前端·后端·go
喵个咪1 天前
GoWind CMS 拆解:发布审批——多级推进、逐级角色与全程留痕
前端·后端·go
喵个咪1 天前
GoWind Flow 拆解:一个 Go 工作流引擎与它的双模式设计器
前端·后端·go
喵个咪1 天前
GoWind CMS 拆解:搜索三板斧——高亮、联想、同义词
后端·搜索引擎·go
喵个咪1 天前
GoWind 拆解:AI Agent 编排引擎——波次并行、子图递归与人工审批
人工智能·后端·go
喵个咪1 天前
GoWind Admin 拆解:打印模板设计器——从画布到服务端 PDF 的完整打印链路
前端·后端·go
喵个咪1 天前
GoWind Admin 拆解:BI 大屏设计器——schema 即 JSON、resolver 注入与三张快照表的口径谱系
大数据·后端·go
Warson_L2 天前
高性能架构设计
python·go