go如何比较两个对象相同

reaperhero · · 1999 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Go 语言中提供了一个函数可以完成此项功能: ```golang func DeepEqual(x, y interface{}) bool ``` `DeepEqual` 函数的参数是两个 `interface`,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。 先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。 ```golang 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| 两者存储的具体值“深度”相等 | |Map|1、都为 nil;2、非空、长度相等,指向同一个 map 实体对象,或者相应的 key 指向的 value “深度”相等 | |Pointer|1、使用 == 比较的结果相等;2、指向的实体“深度”相等 | |Slice|1、都为 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 具体的内容。 对于“有环”的类型,比如循环链表,比较两者是否“深度”相等的过程中,需要对已比较的内容作一个标记,一旦发现两个指针之前比较过,立即停止比较,并判定二者是深度相等的。这样做的原因是,及时停止比较,避免陷入无限循环。 来看源码: ```golang 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` 类型的比较: ```golang // 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,记录递归过程中,比较过的“对”: ```golang type visit struct { a1 unsafe.Pointer a2 unsafe.Pointer typ Type } map[visit]bool ``` 比较过程中,一旦发现比较的“对”,已经在 map 里出现过的话,直接判定“深度”比较结果的是 `true`。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1999 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传