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,只是指向了原有指针地址。

相关推荐
研究司马懿5 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大19 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo