继续闲来写写码,这次来介绍一下半年多以前写的一个库,最近工作中发现真的有用,还是值得推荐一下的。
背景
这个库是 github.com/huandu/go-clone,主要用途是对任意的 Go 结构进行深拷贝,创造一个内容完全相同的副本,得到的值可通过 reflect.DeepEqual 检查。
这个功能看起来挺常用的,不过很奇怪在 Go 世界里面可用的实现却很少,在动手实现之前我调查了几个类似的库或者可用来做深拷贝:
如果大家有看到其他类似功能的库,欢迎留言,我会去研究学习一下。当前所看到的几个库都不太能满足需求。
实现思路
Clone(v interface{}) interface{}
val := reflect.ValueOf(v)vval.Kind()reflect.Newreflect.MakeMapreflect.MakeSlicereflect.MakeChanval.Set*
structval.NumField()val.Field(i)
思路看起来很简单,似乎都是些体力活,但做了之后就会发现有一些特殊情况还得多加小心,真要实现好不容易。
处理递归数据
当我们使用循环链表的时候就会遇到递归数据。一个首尾相连的链表,如果一直跟着指针深拷贝所有数据,那么深拷贝函数一定会陷入死循环而无法退出。
下面是一个例子。
node1 -> node2 -> node3 -> node1 -> ...
CloneNext *ListNode
为了解决这个问题,应该使用经典的有向图检查环路的方法来实现,需要记下那些会产生循环的类型的访问记录,下次再访问到同样的数据时直接返回之前记录的结果即可打破循环。
mapslicestructinterface
structstructinterface
interfaceTinterface{}TT
reflect.DeepEqualmap[interface{}]struct{}interface{}mapKeyTypemapKeyTypemap
reflect.DeepEqual
reflect.ValuePointerunsafe.Pointermapslicereflect.DeepEqualv1v2visitClone
unsafe.PointeruintptrCloneunsafe.Pointer
slicesliceslicereflect.DeepEqualCloneslice
综上,最后采用如下结构来记录访问过的值。
extraslice
visitMapvisitMap
visitMapclone.Cloneclone.Slowly
小结
以上内容已经可以帮助我们了解如何实现一个简单可用的深拷贝的工具库了, 这篇文章就暂时到此为止。
struct