• golang中如何比较struct,slice,map是否相等以及几种对比方法的区别


    一、前言

          对比两个struct或者mapslice是否相等是大家经常会有的需求,想必大家也都接触过很多对比的方式,比如==,reflect.DeepEqual(),cmp.Equal()等。

          这么多种对比方式,适用场景和优缺点都有哪些呢?为什么可以用==,有的却不可以呢?除了这三个,还有其他的方式可以判断相等吗?问题多多,且一起研究研究。

    二、== 的对比方式

    参考:深入理解Go之==

    1、golang的四大类型

    golang 中的数据类型可以分为以下 4 大类:

    基本类型:整型(
    int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)、浮点数(
    float32/float64)、复数类型(
    complex64/complex128)、字符串(
    string)。
    复合类型(又叫聚合类型):数组和结构体类型。
    引用类型:切片(slice)、map、channel、指针。
    接口类型:如error :类型一致且是基本类型,值相等的时候,才能==,非基本类型会panic panic: runtime error: comparing uncomparable type []int
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2、== 适用的类型

          我们日常开发中,经常见到使用==的类型一般是:string,int等基本类型。struct有时候可以用有时候不可以。slice和map使用 ==会报错.

    int1:=10
    int2:=10
    str1:="11"
    str2:="11"
    if int1 == int2{}
    if str1 == str2{}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    intstring是值类型,我们直接对比他们的值。当然,前提是类型要一致,类型不一致编译过不了。

    3、slice和map使用 ==

    首先golang里面有种说法:

    切片之间不允许比较。切片只能与nil值比较。
    map之间不允许比较。map只能与nil值比较。
    
    • 1
    • 2

    那么我们分别测试下发现:

    1map比较会报错:map can only be compared to nil2)切片报错:the operator == is not defined on []int64
        slice can only be compared to nil
    
    • 1
    • 2
    • 3

    (1)那么两个nil是否可以==比较呢

    答案是不能:invalid operation: nil == nil (operator == not defined on nil)
    
    • 1

    (2)slice,map使用==的场景

    就像上面说的,slice和map只能和nil使用==,他们各自之间是不可以的。

    s1 := []int64{1, 2}
    if s1 == nil {} //编辑器不会提示报错
    
    • 1
    • 2

    (3)为什么slice和map不可以

          因为slicemap不止是需要比较值,还需要比较lencap,层级比较深的话还需要递归比较,不是简单的==就可以比较的,具体的我们可以参照reflect.DeepEqual()中实现的切片对比代码。 另外有大佬也说会出现循环引用的问题。

    4、channel使用 ==

    channel是引用类型,对比的是存储数据的地址。channel是可以使用==的,只要类型一样就可以。

    ch1 := make(chan int, 1)
    ch2 := ch1
    if cha2 == cha1{fmt.Println("true")}
    
    • 1
    • 2
    • 3

    5、struct结构体使用==

    (1)首先要明确几点:

    1)结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。
    实例化就是根据结构体定义的格式创建一份与格式一致的内存区域,结构体实例与实例间的
    内存是完全独立的
    2)对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作
    因此:go中的结构体: v = Struct {}, v = &Struct{} 这个两种写法是等价的
    
    • 1
    • 2
    • 3
    • 4
    • 5

    结构体这里比较复杂一些。我们可以先下结论:

    1、简单结构的结构体,里面都是值类型或者指针的话,是可以使用 ==2、结构体中含有slice或者map,都是不可以用==
    
    • 1
    • 2

    (2)简单结构体的==

    type Value struct {
        Name   string
        Gender string
        }
     
        func main() {
            v1 := Value{Name: "test", Gender: "男"}
            v2 := Value{Name: "test", Gender: "男"}
            if v1 == v2 {
                fmt.Println("true")
                return
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (3)带指针的结构体==

        首先要明确,指针类型指向的地址不一样,肯定是没办法比较的,如果地址一样,那么也可以用==
        type Value struct {
        Name   string
        Gender *string
        }
     
        func main() {
             Gender :=new(string) //下面赋值用的同一个变量,地址相同
            v1 := Value{Name: "test", Gender: Gender}
            v2 := Value{Name: "test", Gender: Gender}
            if v1 == v2 {
                fmt.Println("true")
                return
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (4)强制转换类型的==

    type StructA struct {
        Name string
        }
     
        type StructB struct {
            Name string
        }
     
        func main() {
            s1 := StructA{Name: "test1"}
            s2 := StructB{Name: "test1"}
            if s1 == StructA(s2) {
                fmt.Println("true")
                return
            }    
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

          那复杂类型的结构体呢,要如何对比相等?包括slicemap如何对比相等呢?接下里就引入其他的对比方式。

    三、reflect.DeepEqual() 和cmp.Equal()

    1、reflect.DeepEqual()

    reflect包提供的深度对比(递归)的方法,适用于go中的slice,map,struct,function的对比。

    (1)对比规则

      	相同类型的值是深度相等的,不同类型的值永远不会深度相等。
        当数组值(array)的对应元素深度相等时,数组值是深度相等的。
        当结构体(struct)值如果其对应的字段(包括导出和未导出的字段)都是深度相等的,则该值是深度相等的。
        当函数(func)值如果都是零,则是深度相等;否则就不是深度相等。
        当接口(interface)值如果持有深度相等的具体值,则深度相等。
        当切片(slice)序号相同,如果值,指针都相等,那么就是深度相等的
        当哈希表(map)相同的key,如果值,指针都相等,那么就是深度相等的。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2)对比实例

    通过规则可以知道,reflect.DeepEqual是可以比较struct的,同时也可以用来比较slice和mapfunc main() {
            s1 := StructA{Name: "test", Hobby: []string{"唱", "跳"}}
            s2 := StructA{Name: "test", Hobby: []string{"唱", "跳"}}
            if reflect.DeepEqual(s1, s2) {
                fmt.Println("struct true")
            }
            mp1 := map[int]int{1: 10, 2: 20}
    	mp2 := map[int]int{1: 10, 2: 20}
             if ok := reflect.DeepEqual(mp1, mp2);ok {
    		fmt.Println("mp1 == mp2!")
    	    } else {
    		fmt.Println("mp1 != mp2!")
    	    }
        }
      
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2、cmp.Equal()

    go-cmpGoogle 开源的比较库,它提供了丰富的选项。

    这个包旨在成为 reflect.DeepEqual比较两个值在语义上是否相等的更强大和更安全的替代方案。

    参考:在测试中使用go-cmp

    (1)对比规则:

    1.在经过路径过滤,值过滤和类型过滤之后,会生一些忽略、转换、比较选项,如果选项中存在忽略,
    则忽略比较,如果转换器和比较器的数据大于1,则会panic(因为比较操作不明确)。如果选项中
    存在转换器,则调用转换器转换当前值,再递归调用转换器输出类型的Equal。如果包含一个比较器。
    则比较使用比较器比较当前值。否则进入下一比较阶段。
    
    2.如果比较值有一个(T) Equal(T) bool 或者 (T) Equal(I) bool,那么,即使x与y是nil,
    也会调用x.Equal(y)做为结果。如果不存在这样的方法,则进入下一阶段。
    
    3.在最后阶段,Equal方法尝试比较x与y的基本类型。使用go语言的 == 比较基本类型
    (bool, intX, float32,float64, complex32,complex64, string, chan)。
    
    4.在比较struct时,将递归的比较struct的字段。如果结构体包含未导出的字段,函数会panic。
    可以通过指定cmpopts.IgnoreUnexported来忽略未导出的字段,也可以使用
    cmp.AllowUnexported来指定比较未导出的字段。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (2)代码

    // 传入要对比的结构即可
    func Equal(x, y interface{}, opts ...Option) bool {
    	s := newState(opts)
    	s.compareAny(rootStep(x, y))
    	return s.result.Equal()
    }
    
    //获取diff差异
    func Diff(x, y interface{}, opts ...Option) string {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3、cmp和DeepEqual的区别?

    安全:cmp.Equal()函数不会比较未导出字段(即字段名首字母小写的字段)。遇到未导出字段,
    cmp.Equal()直接panic,reflect.DeepEqual()会比较未导出的字段。
    
    强大:cmp.Equal()函数提供丰富的函数参数,让我们可以实现:忽略部分字段,比较零值,
    转换某些值,允许误差等。
    
    共同点:两种比较类型,都会比较:对象类型,值,指针地址等。切片会按照索引比较值,
    map是按照key相等比较值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    四、其他对比方式

    1、testify库的 assert.Equal()

    参考:Go 每日一库之 testify

          我们在写单测的时候,经常会使用assert.Equal() 来做对比,原理如下:

    1[]byte类型就使用bytes.Equal()2)非[]byte类型,使用reflect.DeepEqual()3)不相等则获取diff
    
    • 1
    • 2
    • 3

    2、bytes.Equal()

          标准库中针对特定类型进行比较相等性的函数或方法。例如:想比较两个 byte slice 就可以使用 bytes.Compare 函数。

          reflect.DeepEqual()函数在比对slice的时候,如果发现是uint8类型,也就是[]byte类型,也会调用bytes包的对比方法

    case Slice:
            // Special case for []byte, which is common.
            if v1.Type().Elem().Kind() == Uint8 {
             return bytealg.Equal(v1.Bytes(), v2.Bytes())
            }
            //转化为string之间的对比
            func Equal(a, b []byte) bool {
             return string(a) == string(b)
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    五、效率对比及总结

    1、效率对比

    效率对比参考:Golang几种对象比较方法

    1、简单类型的==对比速度最快
    2、复杂类型,自己知道结构之后写的自定义对比速度次之
    3、复杂结构且不确定结构的,使用cmp.Equal()或者reflect.DeepEqual()都可以,就是效率低点
    4、assert.Equal()底层使用的就是reflect.DeepEqual()
    
    • 1
    • 2
    • 3
    • 4

    2、总结

          我们发现对比的两个结构是否相等,方式很多,效率也有高有低。选择合适自己需求的最重要。相对来说,cmp包是要更安全且可操作性更强一点,主要是看大家的喜好了。

    end

  • 相关阅读:
    Android数据库基础
    opencv 入门学习
    使用vue扫描扫描仪图像
    【JavaWeb】之Http协议
    [JavaScript 刷题] 堆 - 数据流中的第 K 大元素, leetcode 703
    kaggle新赛:写作质量预测大赛【数据挖掘】
    Day09 SSM第九次笔记---SpringBoot基础部分学习
    java写的教师信息管理系统代码
    数据结构与算法学习(day4)——解决实际问题
    人脸活体检测技术的应用,有效避免人脸识别容易被攻击的缺陷
  • 原文地址:https://blog.csdn.net/LJFPHP/article/details/126063715