如何go语言比较两个对象是否深度相同

作者:reaperhero? 时间:2024-02-18 21:21:58 

前言:

func DeepEqual(x, y interface{}) bool

DeepEqual 函数的参数是两个 interface,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。

先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。

type MyInt int
type YourInt int
func main() {
   m := MyInt(1)
   y := YourInt(1)
   fmt.Println(reflect.DeepEqual(m, y)) // false
}

上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是 MyInt,后者是 YourInt,因此两者不是“深度”相等。

在源码里,有对 DeepEqual 函数的非常清楚地注释,列举了不同类型,DeepEqual 的比较情形,这里做一个总结:

类型深度相等情形
Array相同索引处的元素“深度”相等
Struct相应字段,包含导出和不导出,“深度”相等
Func只有两者都是 nil 时
Interface两者存储的具体值“深度”相等
Map1、都为 nil;2、非空、长度相等,指向同一个 map 实体对象,或者相应的 key 指向的 value “深度”相等
Pointer1、使用 == 比较的结果相等;2、指向的实体“深度”相等
Slice1、都为 nil;2、非空、长度相等,首元素指向同一个底层数组的相同元素,即 &x[0] == &y[0] 或者 相同索引处的元素“深度”相等
numbers, bools, strings, and channels使用 == 比较的结果为真

一般情况下,DeepEqual 的实现只需要递归地调用 == 就可以比较两个变量是否是真的“深度”相等。

但是,有一些异常情况:比如 func 类型是不可比较的类型,只有在两个 func 类型都是 nil 的情况下,才是“深度”相等;float 类型,由于精度的原因,也是不能使用 == 比较的;包含 func 类型或者 float 类型的 struct, interface, array 等。

对于指针而言,当两个值相等的指针就是“深度”相等,因为两者指向的内容是相等的,即使两者指向的是 func 类型或者 float 类型,这种情况下不关心指针所指向的内容。

同样,对于指向相同 slice, map 的两个变量也是“深度”相等的,不关心 slice, map 具体的内容。

对于“有环”的类型,比如循环链表,比较两者是否“深度”相等的过程中,需要对已比较的内容作一个标记,一旦发现两个指针之前比较过,立即停止比较,并判定二者是深度相等的。这样做的原因是,及时停止比较,避免陷入无限循环。

来看源码:

func DeepEqual(x, y interface{}) bool {
   if x == nil || y == nil {
       return x == y
   }
   v1 := ValueOf(x)
   v2 := ValueOf(y)
   if v1.Type() != v2.Type() {
       return false
   }
   return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}

首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true

接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。

最后,最核心的内容在子函数 deepValueEqual 中。

代码比较长,思路却比较简单清晰:核心是一个 switch 语句,识别输入参数的不同类型,分别递归调用 deepValueEqual 函数,一直递归到最基本的数据类型,比较 int,string 等可以直接得出 true 或者 false,再一层层地返回,最终得到“深度”相等的比较结果。

实际上,各种类型的比较套路比较相似,这里就直接节选一个稍微复杂一点的 map 类型的比较:

// deepValueEqual 函数
// ……
case Map:
   if v1.IsNil() != v2.IsNil() {
       return false
   }
   if v1.Len() != v2.Len() {
       return false
   }
   if v1.Pointer() == v2.Pointer() {
       return true
   }
   for _, k := range v1.MapKeys() {
       val1 := v1.MapIndex(k)
       val2 := v2.MapIndex(k)
       if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(v1.MapIndex(k), v2.MapIndex(k), visited, depth+1) {
           return false
       }
   }
   return true
// ……

和前文总结的表格里,比较 map 是否相等的思路比较一致,也不需要多说什么。说明一点,visited 是一个 map,记录递归过程中,比较过的“对”:

type visit struct {
   a1  unsafe.Pointer
   a2  unsafe.Pointer
   typ Type
}
map[visit]bool

比较过程中,一旦发现比较的“对”,已经在 map 里出现过的话,直接判定“深度”比较结果的是 true

来源:https://studygolang.com/articles/35654?utm_source=tuicool&utm_medium=referral

标签:go,比较,对象
0
投稿

猜你喜欢

  • 爬虫框架 Feapder 和 Scrapy 的对比分析

    2022-03-31 00:02:17
  • SQL Server 2008 安装SQLDMO.dll的方法

    2024-01-12 18:13:15
  • sqlserver中触发器+游标操作实现

    2024-01-25 02:37:11
  • Python 使用requests模块发送GET和POST请求的实现代码

    2023-07-19 23:19:27
  • 用browsercam做用户登录后页面的兼容性测试

    2008-12-29 12:05:00
  • pycharm远程开发项目的实现步骤

    2023-07-17 06:01:54
  • Python内置数据结构列表与元组示例详解

    2021-08-17 21:28:14
  • Python多进程之进程同步及通信详解

    2022-01-07 00:29:13
  • C#使用ADO.Net部件来访问Access数据库的方法

    2024-01-27 01:45:42
  • python实现在多维数组中挑选符合条件的全部元素

    2022-06-02 03:43:12
  • 浅谈Go切片的值修改是否会覆盖数组的值 

    2024-04-26 17:18:19
  • sqlserver 索引的一些总结

    2012-08-21 11:03:31
  • 超详细注释之OpenCV更改像素与修改图像通道

    2021-07-01 17:42:45
  • 一文教会你pandas plot各种绘图

    2021-04-29 19:41:11
  • 解决MySQl查询不区分大小写的方法讲解

    2024-01-12 13:49:51
  • PHP程序员玩转Linux系列 使用supervisor实现守护进程

    2023-10-12 14:50:21
  • flash与asp/php/asp.net通信的方法第1/3页

    2023-11-15 03:43:03
  • 解决MySQL报错:You can‘t specify target table ‘region‘ for update in FROM clause

    2024-01-27 08:02:39
  • python生成遍历暴力破解密码的方法

    2021-07-02 21:28:54
  • Python使用Selenium模块模拟浏览器抓取斗鱼直播间信息示例

    2023-06-13 09:23:30
  • asp之家 网络编程 m.aspxhome.com