golang DeepEqual

424 阅读3分钟

有一些引用类型比如切片,map无法用== 判断,golang提供DeepEqual内置方法来比较,下面介绍具体原理

切片

方法中判断切片是否相等具体代码如下,可以看出满足下面两点
1.两个切片len长度相等
2.指向同一个内存地址,即数据源是同一个 or 每一个位置对应元素都相等,两者满足一个就行

case Slice:
   if v1.IsNil() != v2.IsNil() {
      return false
   }
   if v1.Len() != v2.Len() {
      return false
   }
   if v1.Pointer() == v2.Pointer() {
      return true
   }
   for i := 0; i < v1.Len(); i++ {
      if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
         return false
      }
   }
   return true

image.png 上面示例满足指针不相同但是每个元素相同,所以结果为true,另外可以看出即使切片的cap不相等也不影响,源码中没有对cap做限制

struct

image.png 如果两个结构体的属性不一样,包括 1.顺序不一样 2.某个属性名称不一样 3.属性个数不一样 4.struct 命名不一样 都会导致两个结构体的type不一致,源码注释中有一句话

// Values of distinct types are never deeply equal.

两个不同type的值永远是不相等的
所以结构体 属性不相同的情况下,type是不同的,直接会在type判断的时候就返回false,具体代码

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))
}

struct如果是显式命名不一致,说明不是同一个struct,也不一样 image.png 如果两个结构体来自于同一个结构体定义,判断type的时候,肯定是true的,如下

image.png

值的type里面有很多信息需要校验

image.png 测试发现
1.顺序不一致,会导致ptrdata不一样
2.属性名称不一样,比如一个属性值是name ,另一个是name1 ,导致hash不一样
3.属性个数不一样,导致size,ptrdata等多个参数都不一样
4.struct名字不一样,hash不一样
代码下面的deepValueEqual 方法 是具体会判断属性对应的值是否相等的逻辑,结构体type不一致都不会走到这里
如果属性都一样的情况下,进行属性对应值校验的逻辑,比较具体源码如下

case Struct:
   for i, n := 0, v1.NumField(); i < n; i++ {
      if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
         return false
      }
   }
   return true

走到这里说明属性的情况都是一样的,比如第一个属性都是name,第二个是age,在这里就是比较每个属性里面具体的值是否相等
注意:struct里面如果不包含map slice等不能用== 比较的值的时候,可以用==判断
比如下面是可以的

image.png

下面就会编译不通过 image.png 报错

invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)

用注释代码就不会报错

map

从源码可以就看出来
1.先比较键值对数
2.然后比较指针,如果是同一个数据源,直接返回true
3.不是同一数据源,比较每一个key对应的val都相同,返回true

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(val1, val2, visited) {
         return false
      }
   }
   return true

注意:map中即使键值对顺序不一样,也可以相等,因为map本来就是无序的,所以赋值顺序不重要

func

只有在两个函数都是nil的情况下才是相等,返回true

case Func:
   if v1.IsNil() && v2.IsNil() {
      return true
   }
   // Can't do better than this:
   return false