Golang比較兩個slice是否相等

Compare two string slices in GoLang

開發中經常會遇到需要比較兩個slice包含的元素是否完全相等的情況,一般來說有兩個思路:

  • reflect比較的方法
  • 循環遍歷比較的方法

這里用檢查兩個字符串slice是否相等的例子來測試一下這兩種思路的效率<strike>我當然知道你知道reflect方法效率更差啦</strike>

reflect比較的方法

func StringSliceReflectEqual(a, b []string) bool {
    return reflect.DeepEqual(a, b)
}

這個寫法很簡單,就是直接使用reflect包的reflect.DeepEqual方法來比較ab是否相等

這是我最初完成這個需求的方式,年輕嘛,比較天真,覺得reflect啊,高端大氣,而且一行代碼搞定,簡潔有力,給自己默默的點個贊

循環遍歷比較的方法

func StringSliceEqual(a, b []string) bool {
    if len(a) != len(b) {
        return false
    }

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

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

    return true
}

以上是我們項目中的使用的用來比較字符串slice是否相等的一個函數,代碼邏輯很簡單;先比較長度是否相等,false;再比較兩個slice是否都為nil或都不為nil,false;再比較對應索引處兩個slice的元素是否相等,false;前面都為true

需要注意

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

這段代碼是必須的,雖然如果沒有這段代碼,在大多數情況下,上面的函數可以正常工作,但是增加這段代碼的作用是與reflect.DeepEqual的結果保持一致:[]int{} != []int(nil)

Benchmark測試效率

我們都知道Golang中reflect效率很低,所以雖然循環遍歷的方法看起來很啰嗦,但是如果真的效率比reflect方法高很多,就只能忍痛放棄reflect了

使用Benchmark來簡單的測試下二者的效率

Benchmark StringSliceEqual

func BenchmarkEqual(b *testing.B) {
    sa := []string{"q", "w", "e", "r", "t"}
    sb := []string{"q", "w", "a", "s", "z", "x"}
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        StringSliceEqual(sa, sb)
    }
}

Benchmark StringSliceReflectEqual

func BenchmarkDeepEqual(b *testing.B) {
    sa := []string{"q", "w", "e", "r", "t"}
    sb := []string{"q", "w", "a", "s", "z", "x"}
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        StringSliceReflectEqual(sa, sb)
    }
}

上面兩個函數中,b.ResetTimer()一般用于準備時間比較長的時候重置計時器減少準備時間帶來的誤差,這里可用可不用

在測試文件所在目錄執行go test -bench=.命令

Benchmark對比測試結果

在我的電腦,使用循環遍歷的方式,3.43納秒完成一次比較;使用reflect的方式,208納米完成一次操作,效率對比十分明顯

BCE優化

Golang提供BCE特性,即Bounds-checking elimination,關于Golang中的BCE,推薦一篇大牛博客Bounds Check Elimination (BCE) In Golang 1.7

func StringSliceEqualBCE(a, b []string) bool {
    if len(a) != len(b) {
        return false
    }

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

    b = b[:len(a)]
    for i, v := range a {
        if v != b[i] {
            return false
        }
    }

    return true
}

上述代碼通過b = b[:len(a)]處的bounds check能夠明確保證v != b[i]中的b[i]不會出現越界錯誤,從而避免了b[i]中的越界檢查從而提高效率

類似的,完成Benchmark函數

func BenchmarkEqualBCE(b *testing.B) {
    sa := []string{"q", "w", "e", "r", "t"}
    sb := []string{"q", "w", "a", "s", "z", "x"}
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        StringSliceEqualBCE(sa, sb)
    }
}

在測試文件所在目錄執行go test -bench=.命令

Benchmark對比測試結果

看起來提升并不明顯啊,而且在運行多次Benchmard測試的過程中還出現過BenchmarkEqualBCE效率低于BenchmarkEqual的情況(╯‵□′)╯︵┴─┴

可能是我對BCE的理解姿勢有問題亦或是Golang BCE自身的問題,總之這個如果我有了更深入的理解會再次更新

但是隨著Golang的優化,應該會越來越明顯吧 ┬─┬ ノ( ' - 'ノ)

結論

推薦使用StringSliceEqualBCE形式的比較函數<strike>反正不用reflect比較就不會被領導罵被同事噴</strike>

代碼已經上傳到了我的Github倉庫可以下載測試

最后當然是希望喜歡這篇文章的朋友為我的個人博客增加點人氣啦

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 官方中文版原文鏈接 感謝社區中各位的大力支持,譯者再次奉上一點點福利:阿里云產品券,享受所有官網優惠,并抽取幸運大...
    HetfieldJoe閱讀 6,570評論 3 22
  • Go語言做Web編程非常方便,并且在開發效率和程序運行效率方面都非常優秀。相比于Java,其最大的優勢就是簡便易用...
    暗黑破壞球嘿哈閱讀 9,036評論 6 66
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,740評論 18 399
  • 很幸運,當我想自己認識男朋友時,不想讓別人介紹男朋友時,他出現了。自打在北大第一次見面后,我就有了等他的念頭。很自...
    伊艾閱讀 234評論 0 0
  • 群貓爭寵,你算哪一只?
    云朵兒z閱讀 565評論 4 3