golang中判斷兩個slice是否相等

在golang中咱們能夠輕鬆地經過==來判斷兩個數組(array)是否相等,但遺憾的是slice並無相關的運算符,當須要判斷兩個slice是否相等時咱們只能另尋捷徑了。golang

slice相等的定義

咱們選擇最多見的需求,也就是當兩個slice的類型和長度相同,且相等下標的值也是相等的,好比:數組

a := []int{1, 2, 3}
b := []int{1, 2, 3}
c := []int{1, 2}
d := []int{1, 3, 2}

上述代碼中ab是相等的,c由於長度和a不一樣因此不相等,d由於元素的排列順序和a不一樣因此也不相等。app

判斷兩個[]byte是否相等

爲何要單獨將[]byte列舉出來呢?ide

由於標準庫提供了優化的比較方案,再也不須要咱們造輪子了:性能

package main

import (
    "bytes"
    "fmt"
)

func main() {
    a := []byte{0, 1, 3, 2}
    b := []byte{0, 1, 3, 2}
    c := []byte{1, 1, 3, 2}

    fmt.Println(bytes.Equal(a, b))
    fmt.Println(bytes.Equal(a, c))
}

運行結果以下:測試

bytes slice

使用reflect判斷slice是否相等

在判斷類型不是[]byte的slice時,咱們還能夠藉助reflect.DeepEqual,它用於深度比較兩個對象包括它們內部包含的元素是否都相等:優化

func DeepEqual(x, y interface{}) bool

DeepEqual reports whether x and y are 「deeply equal,」 defined as follows. Two values of identical type are deeply equal if one of the following cases applies. Values of distinct types are never deeply equal.
...
Slice values are deeply equal when all of the following are true: they are both nil or both non-nil, they have the same length, and either they point to the same initial entry of the same underlying array (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) are not deeply equal.ui

這段話的意思不難理解,和咱們在本文最開始時討論的如何肯定slice相等的原則是同樣的,只不過它藉助了一點運行時的「黑魔法」。code

看例子:對象

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := []int{1, 2, 3, 4}
    b := []int{1, 3, 2, 4}
    c := []int{1, 2, 3, 4}
    fmt.Println(reflect.DeepEqual(a, b))
    fmt.Println(reflect.DeepEqual(a, c))
}

手寫判斷

在golang中使用reflect一般須要付出性能代價,若是咱們肯定了slice的類型,那麼本身實現slice的相等判斷相對來講也不是那麼麻煩:

func testEq(a, b []int) bool {
    // If one is nil, the other must also be nil.
    if (a == nil) != (b == nil) {
        return false;
    }

    if len(a) != len(b) {
        return false
    }

    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }

    return true
}

測試代碼:

package main

import "fmt"

func main() {
    a := []int{1, 2, 3, 4}
    b := []int{1, 3, 2, 4}
    c := []int{1, 2, 3, 4}
    fmt.Println(testEq(a, b))
    fmt.Println(testEq(a, c))
}

運行結果:

下面咱們對後兩種方案作個簡單的性能測試,咱們測試兩個不相等但很類似的擁有20個元素的slice,這是在平常開發中較常見的情景:

func BenchmarkTestEq(b *testing.B) {
    a := []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
    c := []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21}
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = testEq(a, c)
    }
}

func BenchmarkDeepEqual(b *testing.B) {
    a := []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
    c := []uint32{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21}
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = reflect.DeepEqual(a, c)
    }
}

固然這個測試只能反應出有限的信息,正常狀況下應該給出更全面的測試用例。不過在咱們的演示中反射仍然付出了驚人的性能代價:

若是咱們把slice的長度設爲1000,那麼差距就會更加明顯:

func genDiffSlice(size int) ([]uint32, []uint32) {
    a := make([]uint32, 0, size)
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < size; i++ {
        a = append(a, rand.Uint32())
    }
    b := make([]uint32, len(a))
    copy(b, a)
    b[len(b)-1] = rand.Uint32()
    return a, b
}

func BenchmarkTestEq2(b *testing.B) {
    a, c := genDiffSlice(1000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = testEq(a, c)
    }
}

func BenchmarkDeepEqual2(b *testing.B) {
    a, c := genDiffSlice(1000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _ = reflect.DeepEqual(a, c)
    }
}

本身手寫判斷的性能更好,可是有個顯而易見的弊端,當咱們有多種類型的slice時咱們就不得不編寫不一樣版本的testEq,而它們惟一的不一樣僅僅只有slice的類型。

不過等到go2的泛型可使用的時候,這樣的弊端也就不復存在了,如今咱們須要的是在代碼的複雜度和運行性能上作出權衡。

參考

Checking the equality of two slices

相關文章
相關標籤/搜索