Golang判断struct/slice/map是否相等以及对比的方法总结

目录
  • 前言
  • == 的对比方式
    • == 适用的类型
    • slice和map使用 ==
    • channel使用 ==
    • struct结构体使用==
  • reflect.DeepEqual() 和cmp.Equal()
    • reflect.DeepEqual()
    • cmp.Equal()
    • cmp和DeepEqual的区别
  • 性能比较
  • 总结

前言

平时开发中对比两个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相等比较值。

性能比较

简单类型的==对比速度最快

复杂类型,自己知道结构之后写的自定义对比速度次之

复杂结构且不确定结构的,使用cmp.Equal()或者reflect.DeepEqual()都可以,就是效率低点

assert.Equal()底层使用的就是reflect.DeepEqual()

总结

对于比较两个struct或者map,slice是否相等,方式很多,效率也有差异。选择合适自己需求的最重要。相对来说,cmp包是要更安全且可操作性更强一点。

到此这篇关于Golang判断struct/slice/map是否相等以及对比的方法总结的文章就介绍到这了,更多相关Golang struct slice map内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go遍历struct,map,slice的实现

    遍历结构体 如何实现遍历结构体字段? 好吧,言归正传!举个例子: demo1: package main import ( "fmt" "reflect" ) type Student struct { name string age int } func main() { v := reflect.ValueOf(Student{"乔峰", 29}) count := v.NumField() for i := 0; i < count;

  • go 判断两个 slice/struct/map 是否相等的实例

    可以通过 reflect.DeepEqual 比较两个 slice/struct/map 是否相等: package main import ( "fmt" "reflect" ) type A struct { s string } func main() { a1 := A{s: "abc"} a2 := A{s: "abc"} if reflect.DeepEqual(a1, a2) { fmt.Println(a1,

  • 详解Go开发Struct转换成map两种方式比较

    最近做Go开发的时候接触到了一个新的orm第三方框架gorose,在使用的过程中,发现没有类似beego进行直接对struct结构进行操作的方法,有部分API是通过map进行数据库相关操作,那么就需要我们把struct转化成map,下面是是我尝试两种不同struct转换成map的方法 mport ( "encoding/json" "fmt" "reflect" "time" ) type Persion struct { I

  • Golang判断struct/slice/map是否相等以及对比的方法总结

    目录 前言 == 的对比方式 == 适用的类型 slice和map使用 == channel使用 == struct结构体使用== reflect.DeepEqual() 和cmp.Equal() reflect.DeepEqual() cmp.Equal() cmp和DeepEqual的区别 性能比较 总结 前言 平时开发中对比两个struct或者map.slice是否相等是经常遇到的,有很多对比的方式,比如==,reflect.DeepEqual(),cmp.Equal()等也是经常容易混淆

  • Golang实现优雅的将struct转换为map

    目录 前言 方式1:使用JSON序列和反序列化 方式2:使用反射 两种方式对比 封装到工具包 前言 在项目实践中,有时候我们需要将struct结构体转为map映射表,然后基于map做数据裁剪或操作.那么下面我来介绍下常用的两种转换方式,以及对它们做对比,最后采用更优雅的方式,封装到我们的项目工程的工具包里 方式1:使用JSON序列和反序列化 使用json操作的这个方式是比较简单的,容易想到也易实现.直接上代码: package main import ( "encoding/json"

  • golang 实现struct、json、map互相转化

    一.Json和struct互换 (1)Json转struct例子: package main import ( "fmt" "encoding/json" ) type People struct { Name string `json:"name_title"` Age int `json:"age_size"` } func JsonToStructDemo(){ jsonStr := ` { "name_tit

  • Golang中Map按照Value大小排序的方法实例

    目录 起因 探索 实现 第一步 第二步 第三步 总结 总结 Golang中的 map 默认是 无序的 . 起因 最近项目中有这样一个需求: 根据用户当前的坐标点,获取该用户附近的预设城市名称. 这里有一个注意点是,假设这些支持的城市名称是预设的,所以就不能直接通过地图类api根据坐标点获取所在城市名称了. 想到的解决思路是: 获取这几个预设城市的坐标点 App端获取用户当前坐标点 分别计算得到该用户坐标点距离各个预设城市的坐标点距离 然后计算得到其中距离最小的一项 这个坐标点对应的城市就是所求

  • golang 并发安全Map以及分段锁的实现方法

    涉及概念 并发安全Map 分段锁 sync.Map CAS ( Compare And Swap ) 双检查 分断锁 type SimpleCache struct { mu sync.RWMutex items map[interface{}]*simpleItem } 在日常开发中, 上述这种数据结构肯定不少见,因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁. 之前使用过两个本地内存缓存的开源库, gcache, cache2go,其中

  • Golang中的Slice与数组及区别详解

    在golang中有数组和Slice两种数据结构,Slice是基于数组的实现,是长度动态不固定的数据结构,本质上是一个对数组字序列的引用,提供了对数组的轻量级访问.那么我们今天就给大家详细介绍下Golang中的Slice与数组, 1.Golang中的数组 数组是一种具有固定长度的基本数据结构,在golang中与C语言一样数组一旦创建了它的长度就不允许改变,数组的空余位置用0填补,不允许数组越界. 数组的一些基本操作:      1.创建数组: func main() { var arr1 = [.

  • golang中使用sync.Map的方法

    背景 go中map数据结构不是线程安全的,即多个goroutine同时操作一个map,则会报错,因此go1.9之后诞生了sync.Map sync.Map思路来自java的ConcurrentHashMap 接口 sync.map就是1.9版本带的线程安全map,主要有如下几种方法: Load(key interface{}) (value interface{}, ok bool) //通过提供一个键key,查找对应的值value,如果不存在,则返回nil.ok的结果表示是否在map中找到值

  • 浅谈Golang 切片(slice)扩容机制的原理

    我们知道 Golang 切片(slice) 在容量不足的情况下会进行扩容,扩容的原理是怎样的呢?是不是每次扩一倍?下面我们结合源码来告诉你答案. 一.源码 Version : go1.15.6  src/runtime/slice.go //go1.15.6 源码 src/runtime/slice.go func growslice(et *_type, old slice, cap int) slice { //省略部分判断代码 //计算扩容部分 //其中,cap : 所需容量,newcap

  • GO中 分组声明与array, slice, map函数

    目录 iota 枚举 Go 程序设计的一些规则 数组 切片 map make.new 操作 前言: 在 Go 语言中,同时声明多个常量.变量,或者导入多个包时,可采用分组的方式进行声明. 例如下面的代码: import "fmt" import "os" const i = 100 const pi = 3.1415 const prefix = "Go_" var i int var pi float32 var prefix string1

  • golang 使用sort.slice包实现对象list排序

    目录 1.sort.Sort介绍 1.1分析内置sort包 1.2分析sort.go 2.使用方法 2.1基础类型排序 2.2对象排序(单一字段) 2.3对象排序(多字段) 3.sort.Slice介绍 3.1使用方法 3.2运行 1.sort.Sort介绍 使用sort.Slice进行排序,因为slice把struct抽象化了,且slice封装过了,简单的基础类型可以使用sort,使用sort排序需要重写三个interface,不想学习sort排序的可以直接看第三步 这里将对比sort跟sli

随机推荐