一、前言
對比兩個struct或者map,slice是否相等是大家經(jīng)常會有的需求,想必大家也都接觸過很多對比的方式,比如==,reflect.DeepEqual(),cmp.Equal()等。
這么多種對比方式,適用場景和優(yōu)缺點(diǎn)都有哪些呢?為什么可以用==,有的卻不可以呢?除了這三個,還有其他的方式可以判斷相等嗎?問題多多,且一起研究研究。
二、== 的對比方式
1、golang的四大類型
golang中的數(shù)據(jù)類型可以分為以下 4 大類:
基本類型:整型( int/uint/int8/uint8/int16/uint16/int32/uint32/int64/uint64/byte/rune等)、浮點(diǎn)數(shù)( float32/float64)、復(fù)數(shù)類型( complex64/complex128)、字符串( string)。 復(fù)合類型(又叫聚合類型):數(shù)組和結(jié)構(gòu)體類型。 引用類型:切片(slice)、map、channel、指針。 接口類型:如error :類型一致且是基本類型,值相等的時候,才能==,非基本類型會panic panic: runtime er
ror: comparing uncomparable type []int
2、== 適用的類型
我們?nèi)粘i_發(fā)中,經(jīng)常見到使用==的類型一般是:string,int等基本類型。struct有時候可以用有時候不可以。slice和map使用 ==會報(bào)錯.
int1:=10 int2:=10 str1:="11" str2:="11" if int1 == int2{} if str1 == str2{}
int和string是值類型,我們直接對比他們的值。當(dāng)然,前提是類型要一致,類型不一致編譯過不了。
3、slice和map使用 ==
首先golang里面有種說法:
切片之間不允許比較。切片只能與nil值比較。 map之間不允許比較。map只能與nil值比較。
那么我們分別測試下發(fā)現(xiàn):
(1)map比較會報(bào)錯:map can only be compared to nil (2)切片報(bào)錯:the operator == is not defined on []int64 slice can only be compared to nil
(1)那么兩個nil是否可以==比較呢
答案是不能:invalid operation: nil == nil (operator == not defined on nil)
(2)slice,map使用==的場景
就像上面說的,slice和map只能和nil使用==,他們各自之間是不可以的。
s1 := []int64{1, 2} if s1 == nil {} //編輯器不會提示報(bào)錯
(3)為什么slice和map不可以
因?yàn)閟lice和map不止是需要比較值,還需要比較len和cap,層級比較深的話還需要遞歸比較,不是簡單的==就可以比較的,具體的我們可以參照reflect.DeepEqual()中實(shí)現(xiàn)的切片對比代碼。另外有大佬也說會出現(xiàn)循環(huán)引用的問題。
4、channel使用 ==
channel是引用類型,對比的是存儲數(shù)據(jù)的地址。channel是可以使用==的,只要類型一樣就可以。
ch1 := make(chan int, 1) ch2 := ch1 if cha2 == cha1{fmt.Println("true")}
5、struct結(jié)構(gòu)體使用==
(1)首先要明確幾點(diǎn):
1)結(jié)構(gòu)體的定義只是一種內(nèi)存布局的描述,只有當(dāng)結(jié)構(gòu)體實(shí)例化時,才會真正地分配內(nèi)存。 實(shí)例化就是根據(jù)結(jié)構(gòu)體定義的格式創(chuàng)建一份與格式一致的內(nèi)存區(qū)域,結(jié)構(gòu)體實(shí)例與實(shí)例間的 內(nèi)存是完全獨(dú)立的 2)對結(jié)構(gòu)體進(jìn)行&取地址操作時,視為對該類型進(jìn)行一次 new 的實(shí)例化操作 因此:go中的結(jié)構(gòu)體: v = Struct {}, v = &Struct{} 這個兩種寫法是等價的
結(jié)構(gòu)體這里比較復(fù)雜一些。我們可以先下結(jié)論:
1、簡單結(jié)構(gòu)的結(jié)構(gòu)體,里面都是值類型或者指針的話,是可以使用 ==的 2、結(jié)構(gòu)體中含有slice或者map,都是不可以用==
(2)簡單結(jié)構(gòu)體的==
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 } }
(3)帶指針的結(jié)構(gòu)體==
首先要明確,指針類型指向的地址不一樣,肯定是沒辦法比較的,如果地址一樣,那么也可以用==
首先要明確,指針類型指向的地址不一樣,肯定是沒辦法比較的,如果地址一樣,那么也可以用== 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 } }
(4)強(qiáng)制轉(zhuǎn)換類型的==
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 } }
那復(fù)雜類型的結(jié)構(gòu)體呢,要如何對比相等?包括slice和map如何對比相等呢?接下里就引入其他的對比方式。
三、reflect.DeepEqual() 和cmp.Equal()
1、reflect.DeepEqual()
reflect包提供的深度對比(遞歸)的方法,適用于go中的slice,map,struct,function的對比。
(1)對比規(guī)則
相同類型的值是深度相等的,不同類型的值永遠(yuǎn)不會深度相等。 當(dāng)數(shù)組值(array)的對應(yīng)元素深度相等時,數(shù)組值是深度相等的。 當(dāng)結(jié)構(gòu)體(struct)值如果其對應(yīng)的字段(包括導(dǎo)出和未導(dǎo)出的字段)都是深度相等的,則該值是深度相等的。 當(dāng)函數(shù)(func)值如果都是零,則是深度相等;否則就不是深度相等。 當(dāng)接口(interface)值如果持有深度相等的具體值,則深度相等。 當(dāng)切片(slice)序號相同,如果值,指針都相等,那么就是深度相等的 當(dāng)哈希表(map)相同的key,如果值,指針都相等,那么就是深度相等的。
(2)對比實(shí)例
通過規(guī)則可以知道,reflect.DeepEqual是可以比較struct的,同時也可以用來比較slice和map。
func 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!") } }
2、cmp.Equal()
go-cmp是Google開源的比較庫,它提供了豐富的選項(xiàng)。
這個包旨在成為reflect.DeepEqual比較兩個值在語義上是否相等的更強(qiáng)大和更安全的替代方案。
參考:在測試中使用go-cmp
(1)對比規(guī)則:
1.在經(jīng)過路徑過濾,值過濾和類型過濾之后,會生一些忽略、轉(zhuǎn)換、比較選項(xiàng),如果選項(xiàng)中存在忽略, 則忽略比較,如果轉(zhuǎn)換器和比較器的數(shù)據(jù)大于1,則會panic(因?yàn)楸容^操作不明確)。如果選項(xiàng)中 存在轉(zhuǎn)換器,則調(diào)用轉(zhuǎn)換器轉(zhuǎn)換當(dāng)前值,再遞歸調(diào)用轉(zhuǎn)換器輸出類型的Equal。如果包含一個比較器。 則比較使用比較器比較當(dāng)前值。否則進(jìn)入下一比較階段。 2.如果比較值有一個(T) Equal(T) bool 或者 (T) Equal(I) bool,那么,即使x與y是nil, 也會調(diào)用x.Equal(y)做為結(jié)果。如果不存在這樣的方法,則進(jìn)入下一階段。 3.在最后階段,Equal方法嘗試比較x與y的基本類型。使用go語言的 == 比較基本類型 (bool, intX, float32,float64, complex32,complex64, string, chan)。 4.在比較struct時,將遞歸的比較struct的字段。如果結(jié)構(gòu)體包含未導(dǎo)出的字段,函數(shù)會panic。 可以通過指定cmpopts.IgnoreUnexported來忽略未導(dǎo)出的字段,也可以使用 cmp.AllowUnexported來指定比較未導(dǎo)出的字段。
(2)代碼
// 傳入要對比的結(jié)構(gòu)即可 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 { }
3、cmp和DeepEqual的區(qū)別?
安全:cmp.Equal()函數(shù)不會比較未導(dǎo)出字段(即字段名首字母小寫的字段)。遇到未導(dǎo)出字段, cmp.Equal()直接panic,reflect.DeepEqual()會比較未導(dǎo)出的字段。 強(qiáng)大:cmp.Equal()函數(shù)提供豐富的函數(shù)參數(shù),讓我們可以實(shí)現(xiàn):忽略部分字段,比較零值, 轉(zhuǎn)換某些值,允許誤差等。 共同點(diǎn):兩種比較類型,都會比較:對象類型,值,指針地址等。切片會按照索引比較值, map是按照key相等比較值
四、其他對比方式
1、testify庫的 assert.Equal()
參考:Go 每日一庫之 testify
我們在寫單測的時候,經(jīng)常會使用assert.Equal()來做對比,原理如下:
(1)[]byte類型就使用bytes.Equal() (2)非[]byte類型,使用reflect.DeepEqual() (3)不相等則獲取diff
2、bytes.Equal()
標(biāo)準(zhǔn)庫中針對特定類型進(jìn)行比較相等性的函數(shù)或方法。例如:想比較兩個byte slice就可以使用bytes.Compare函數(shù)。
reflect.DeepEqual()函數(shù)在比對slice的時候,如果發(fā)現(xiàn)是uint8類型,也就是[]byte類型,也會調(diào)用bytes包的對比方法
case Slice: // Special case for []byte, which is common. if v1.Type().Elem().Kind() == Uint8 { return bytealg.Equal(v1.Bytes(), v2.Bytes()) } //轉(zhuǎn)化為string之間的對比 func Equal(a, b []byte) bool { return string(a) == string(b) }
五、效率對比及總結(jié)
1、效率對比
效率對比參考:Golang幾種對象比較方法
1、簡單類型的==對比速度最快 2、復(fù)雜類型,自己知道結(jié)構(gòu)之后寫的自定義對比速度次之 3、復(fù)雜結(jié)構(gòu)且不確定結(jié)構(gòu)的,使用cmp.Equal()或者reflect.DeepEqual()都可以,就是效率低點(diǎn) 4、assert.Equal()底層使用的就是reflect.DeepEqual()
2、總結(jié)
我們發(fā)現(xiàn)對比的兩個結(jié)構(gòu)是否相等,方式很多,效率也有高有低。選擇合適自己需求的最重要。相對來說,cmp包是要更安全且可操作性更強(qiáng)一點(diǎn),主要是看大家的喜好了。
鏈接:https://juejin.cn/post/7316450414158200847
審核編輯:劉清
-
轉(zhuǎn)換器
+關(guān)注
關(guān)注
27文章
8705瀏覽量
147195 -
比較器
+關(guān)注
關(guān)注
14文章
1651瀏覽量
107221 -
存儲數(shù)據(jù)
+關(guān)注
關(guān)注
0文章
88瀏覽量
14103
原文標(biāo)題:golang中如何比較struct,slice,map是否相等以及幾種對比方法的區(qū)別
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論