前语

平常开发中比照两个struct或许mapslice是否持平是常常遇到的,有许多比照的办法,比如==reflect.DeepEqual()cmp.Equal()等也是常常容易混杂的,这么多种比照办法,适用场景和优缺点都有哪些呢?为什么能够用==,有的却不能够呢?问题多多,今日我们来详细总结一下,感兴趣的小伙伴们能够参阅借鉴,希望对我们能有所帮助。

== 的比照办法

== 适用的类型

信任==判等操作,我们每天都在用。golang中对==的处理有一些细节的当地需求特别注意,==操作最重要的一个条件是:两个操作数类型有必要相同!假如类型不同,那么编译时就会报错。

示例代码:

package main
import "fmt"
func main() {
    var a int32
    var b int64
    // 编译过错:invalid operation a == b (mismatched types int32 and int64)
    fmt.Println(a == b)
}

常常见到运用==的类型一般是:string,int等根本类型。struct有时候能够用有时候不能够。slicemap运用 ==会报错。

slice和map运用 ==

由于slice和map不止是需求比较值,还需求比较len和cap,层级比较深的话还需求递归比较,不是简略的==就能够比较的,所以他们各自之间是不能够直接用==比较的,slice和map只能和nil运用==。

  • 切片之间不允许比较。切片只能与nil值比较。
  • map之间不允许比较。map只能与nil值比较。
s1 := []int64{1, 3}
s2 := []int64{1, 2}
if s1 == nil {} //编辑器不会提示报错
if s1 == s2 {} //编辑器会提示报错

channel运用 ==

channel是引证类型,比照的是存储数据的地址。channel是能够运用==的,只需类型一样就能够。

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

struct结构体运用==

结构体的定义仅仅一种内存布局的描述,只有当结构体实例化时,才会真实地分配内存。

实例化便是依据结构体定义的格局创建一份与格局共同的内存区域,结构体实例与实例间的

内存是完全独立的。对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作

因此:go中的结构体: v = Struct {}, v = &Struct{} 这个两种写法是等价的。

  • 简略结构的结构体,里面都是值类型或许指针的话,是能够运用 ==的
  • 结构体中含有slice或许map,都是不能够用==

示例代码:

package main
import (
    "fmt" 
)
type User struct {
    Name string
    Age  int64
}
type People struct {
    Name string
    Hobby []string
}
func main() {
        p1 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        p2 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        u1 := User{Name: "test", Age:18}
		u2 := User{Name: "test", Age:18}
        if p1 == p2 {
            fmt.Println("p1 ==  p2") //报错
        }
    	if u1 == u2 {
            fmt.Println("u1 ==  u2")
        }
    }

reflect.DeepEqual() 和cmp.Equal()

reflect.DeepEqual()

reflect包供给的深度比照(递归)的办法,适用于go中的slice,map,struct,function的比照。

比照规矩

  • 相同类型的值是深度持平的,不同类型的值永久不会深度持平。
  • 当数组值array的对应元素深度持平时,数组值是深度持平的。
  • 当结构体struct值假如其对应的字段(包括导出和未导出的字段)都是深度持平的,则该值是深度持平的。
  • 当函数func值假如都是零,则是深度持平;否则就不是深度持平。
  • 接口interface值假如持有深度持平的详细值,则深度持平。
  • 当切片slice序号相同,假如值,指针都持平,那么便是深度持平的
  • 哈希map相同的key,假如值,指针都持平,那么便是深度持平的。

经过以上规矩能够看到,reflect.DeepEqual是能够比较struct的,一起也能够用来比较slicemap

示例代码:

package main
import (
    "fmt"
    "reflect"
)
type People struct {
    Name string
    Hobby []string
}
func main() {
        p1 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        p2 := People{Name: "test", Hobby: []string{"唱", "跳"}}
        if reflect.DeepEqual(p1, p2) {
            fmt.Println("struct true")
        }
        mp1 := map[int]int{1: 1, 2: 2}
	    mp2 := map[int]int{1: 1, 2: 2}
        if ok := reflect.DeepEqual(mp1, mp2);ok {
			fmt.Println("mp1 == mp2!")
	    } else {
			fmt.Println("mp1 != mp2!")
	    }
    }

cmp.Equal()

go-cmp是 Google 开源的比较库,它供给了丰富的选项。

比照规矩

  • 在经过途径过滤,值过滤和类型过滤之后,会生一些疏忽、转换、比较选项,假如选项中存在疏忽,则疏忽比较,假如转换器和比较器的数据大于1,则会panic(由于比较操作不明确)。假如选项中存在转换器,则调用转换器转换当前值,再递归调用转换器输出类型的Equal。假如包括一个比较器。则比较运用比较器比较当前值。否则进入下一比较阶段。
  • 假如比较值有一个(T) Equal(T) bool 或许 (T) Equal(I) bool,那么,即使x与y是nil,也会调用x.Equal(y)做为结果。假如不存在这样的办法,则进入下一阶段。
  • 在最后阶段,Equal办法测验比较x与y的根本类型。运用go言语的 == 比较根本类型(bool, intX, float32,float64, complex32,complex64, string, chan)。
  • 在比较struct时,将递归的比较struct的字段。假如结构体包括未导出的字段,函数会panic能够经过指定cmpopts.IgnoreUnexported来疏忽未导出的字段,也能够运用cmp.AllowUnexported来指定比较未导出的字段。

示例代码:

package main
import (
  "fmt"
  "github.com/google/go-cmp/cmp"
)
type Contact struct {
  Phone string
  Email string
}
type User struct {
  Name    string
  Age     int
  Contact *Contact
}
func main() {
  u1 := User{Name: "test", Age: 18}
  u2 := User{Name: "test", Age: 18}
  fmt.Println("u1 == u2?", u1 == u2)  //true
  fmt.Println("u1 equals u2?", cmp.Equal(u1, u2)) //true
  c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
  c2 := &Contact{Phone: "123456789", Email: "dj@example.com"}
  u1.Contact = c1
  u2.Contact = c1
  fmt.Println("u1 == u2 with same pointer?", u1 == u2) //true
  fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2)) //true
  u2.Contact = c2
  fmt.Println("u1 == u2 with different pointer?", u1 == u2) //false 
  fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2)) //true
}

cmp和DeepEqual的差异?

  • 安全:cmp.Equal()函数不会比较未导出字段(即字段名首字母小写的字段)。遇到未导出字段,cmp.Equal()直接panic,reflect.DeepEqual()会比较未导出的字段。
  • 强壮:cmp.Equal()函数供给丰富的函数参数,让我们能够实现:疏忽部分字段,比较零值,转换某些值,允许误差等。
  • 共同点:两种比较类型,都会比较:目标类型,值,指针地址等。切片会依照索引比较值,map是依照key持平比较值。

功能比较

  1. 简略类型的==比照速度最快
  2. 杂乱类型,自己知道结构之后写的自定义比照速度次之
  3. 杂乱结构且不确定结构的,运用cmp.Equal()或许reflect.DeepEqual()都能够,便是效率低点
  4. assert.Equal()底层运用的便是reflect.DeepEqual()

总结

关于比较两个struct或许map,slice是否持平,办法许多,效率也有差异。选择适宜自己需求的最重要。相对来说,cmp包是要更安全且可操作性更强一点。

本文正在参加「金石计划 . 分割6万现金大奖」