1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        GO編程模式系列(一):切片,接口,時間和性能

        共 7124字,需瀏覽 15分鐘

         ·

        2020-12-30 21:10

        在本篇文章中,我會對Go語言編程模式的一些基本技術(shù)和要點(diǎn),這樣可以讓你更容易掌握Go語言編程。其中,主要包括,數(shù)組切片的一些小坑,還有接口編程,以及時間和程序運(yùn)行性能相關(guān)的話題。

        本文是全系列中第1 / 9篇:Go編程模式

        • Go編程模式:切片,接口,時間和性能
        • Go 編程模式:錯誤處理
        • Go 編程模式:Functional Options
        • Go編程模式:委托和反轉(zhuǎn)控制
        • Go編程模式:Map-Reduce
        • Go 編程模式:Go Generation
        • Go編程模式:修飾器
        • Go編程模式:Pipeline
        • Go 編程模式:k8s Visitor 模式

        目錄

        • Slice深度比較接口編程

        • 接口完整性檢查

        • 時間

        • 性能提示

        • 參考文檔


        Slice

        首先,我們先來討論一下Slice,中文翻譯叫“切片”,這個東西在Go語言中不是數(shù)組,而是一個結(jié)構(gòu)體,其定義如下:

        type?slice?struct{???array?unsafe.Pointer?//指向存放數(shù)據(jù)的數(shù)組指針???len???int???????????//長度有多大???cap???int???????????//容量有多大}

        用圖示來看,一個空的slice的表現(xiàn)如下:

        熟悉C/C++的同學(xué)一定會知道,在結(jié)構(gòu)體里用數(shù)組指針的問題——數(shù)據(jù)會發(fā)生共享!下面我們來看一下slice的一些操作:

        foo?=?make([]int,?5)foo[3]?=?42foo[4]?=?100bar??:=?foo[1:4]bar[1] = 99

        對于上面這段代碼。

        • 首先先創(chuàng)建一個foo的slice,其中的長度和容量都是5
        • 然后開始對foo所指向的數(shù)組中的索引為3和4的元素進(jìn)行賦值
        • 然后,對foo做切片后賦值給bar,再修改bar[1]

        通過上圖我們可以看到,因為foo和bar的內(nèi)存是共享的,所以,foo和bar的對數(shù)組內(nèi)容的修改都會影響到對方。

        接下來,我們再來看一個數(shù)據(jù)操作?append()?的示例:

        a?:=?make([]int,?32)b?:=?a[1:16]a?=?append(a,?1)a[2] = 42

        上面這段代碼中,把?a[1:16]?的切片賦給到了?b?,此時,a?和?b?的內(nèi)存空間是共享的,然后,對?a做了一個?append()的操作,這個操作會讓?a?重新分享內(nèi)存,導(dǎo)致?a?和?b?不再共享,如下圖所示:

        從上圖我們可以看以看到?append()操作讓?a?的容量變成了64,而長度是33。這里,需要重點(diǎn)注意一下——append()這個函數(shù)在?cap?不夠用的時候就會重新分配內(nèi)存以擴(kuò)大容量,而如果夠用的時候不不會重新分享內(nèi)存!

        我們再看來看一個例子:

        funcmain(){?????path?:=?[]byte("AAAA/BBBBBBBBB")???sepIndex?:=?bytes.IndexByte(path,'/’)
        ???dir1?:=?path[:sepIndex] dir2 := path[sepIndex+1:]
        ???fmt.Println("dir1?=>",string(dir1))?//prints:?dir1?=>?AAAA???fmt.Println("dir2?=>",string(dir2))?//prints:?dir2?=>?BBBBBBBBB dir1 = append(dir1,"suffix"...)
        ???fmt.Println("dir1?=>",string(dir1))?//prints:?dir1?=>?AAAAsuffix???fmt.Println("dir2?=>",string(dir2))?//prints:?dir2?=>?uffixBBBB}

        上面這個例子中,dir1?和?dir2?共享內(nèi)存,雖然?dir1?有一個?append()?操作,但是因為 cap 足夠,于是數(shù)據(jù)擴(kuò)展到了dir2?的空間。下面是相關(guān)的圖示(注意上圖中?dir1?和?dir2?結(jié)構(gòu)體中的?cap?和?len?的變化)


        如果要解決這個問題,我們只需要修改一行代碼。

        dir1 := path[:sepIndex]

        修改為

        dir1 := path[:sepIndex:sepIndex]

        新的代碼使用了 Full Slice Expression,其最后一個參數(shù)叫“Limited Capacity”,于是,后續(xù)的?append()?操作將會導(dǎo)致重新分配內(nèi)存。


        深度比較

        當(dāng)我們復(fù)雜一個對象時,這個對象可以是內(nèi)建數(shù)據(jù)類型,數(shù)組,結(jié)構(gòu)體,map……我們在復(fù)制結(jié)構(gòu)體的時候,當(dāng)我們需要比較兩個結(jié)構(gòu)體中的數(shù)據(jù)是否相同時,我們需要使用深度比較,而不是只是簡單地做淺度比較。這里需要使用到反射?reflect.DeepEqual()?,下面是幾個示例。

        import(?????"fmt"???"reflect")
        funcmain(){
        ???v1?:=?data{}???v2?:=?data{}???fmt.Println("v1?==?v2:",reflect.DeepEqual(v1,v2))??//prints:?v1?==?v2:?true???m1?:=?map[string]string{"one":?"a","two":?"b"}???m2?:=?map[string]string{"two":?"b",?"one":?"a"}???fmt.Println("m1?==?m2:",reflect.DeepEqual(m1,?m2))??//prints:?m1?==?m2:?true???s1?:=?[]int{1,?2,?3}???s2?:=?[]int{1,?2,?3}???fmt.Println("s1?==?s2:",reflect.DeepEqual(s1,?s2))??//prints:?s1?==?s2:?true}


        接口編程

        下面,我們來看段代碼,其中是兩個方法,它們都是要輸出一個結(jié)構(gòu)體,其中一個使用一個函數(shù),另一個使用一個“成員函數(shù)”。

        funcPrintPerson(p?*Person){???fmt.Printf("Name=%s,?Sexual=%s,?Age=%d\n",?p.Name,?p.Sexual,?p.Age)}
        func(p?*Person)Print(){???fmt.Printf("Name=%s,?Sexual=%s,?Age=%d\n",?p.Name,?p.Sexual,?p.Age)}
        funcmain(){???var?p?=?Person{???????Name:?"Hao?Chen",???????Sexual:?"Male", Age: 44,???}???PrintPerson(&p)???p.Print()}

        你更喜歡哪種方式呢?在 Go 語言中,使用“成員函數(shù)”的方式叫“Receiver”,這種方式是一種封裝,因為?PrintPerson()本來就是和?Person強(qiáng)耦合的,所以,理應(yīng)放在一起。更重要的是,這種方式可以進(jìn)行接口編程,對于接口編程來說,也就是一種抽象,主要是用在“多態(tài)”,這個技術(shù),在《Go語言簡介(上):接口與多態(tài)》中已經(jīng)講過。在這里,我想講另一個Go語言接口的編程模式。

        首先,我們來看一下,有下面這段代碼:

        type?Country?struct{???Name?string}
        type?City?struct{???Name?string}
        type?Printable?interface{???PrintStr()}func(c?Country)PrintStr(){???fmt.Println(c.Name)}func(c?City)PrintStr(){???fmt.Println(c.Name)}
        c1?:=?Country?{"China"}c2?:=?City?{"Beijing"}c1.PrintStr()c2.PrintStr()

        其中,我們可以看到,其使用了一個?Printable?的接口,而?Country?和?City?都實現(xiàn)了接口方法?PrintStr()?而把自己輸出。然而,這些代碼都是一樣的。能不能省掉呢?

        我們可以使用“結(jié)構(gòu)體嵌入”的方式來完成這個事,如下的代碼所示:

        type?WithName?struct{???Name?string}
        type?Country?struct{???WithName}
        type?City?struct{???WithName}
        type?Printable?interface{???PrintStr()}
        func(w?WithName)PrintStr(){???fmt.Println(w.Name)}
        c1?:=?Country?{WithName{"China"}}c2 := City { WithName{"Beijing"}}c1.PrintStr()c2.PrintStr()

        引入一個叫?WithName的結(jié)構(gòu)體,然而,所帶來的問題就是,在初始化的時候,變得有點(diǎn)亂。那么,我們有沒有更好的方法?下面是另外一個解。

        type?Country?struct{???Name?string}
        type?City?struct{???Name?string}
        type?Stringable?interface{???ToString()string}func(c?Country)ToString()string{???return"Country?=?"?+?c.Name}func(c?City)ToString()string{???return"City?=?"?+?c.Name}
        funcPrintStr(p?Stringable){???fmt.Println(p.ToString())}
        d1?:=?Country?{"USA"}d2?:=?City{"Los?Angeles"}PrintStr(d1)PrintStr(d2)

        上面這段代碼,我們可以看到——我們使用了一個叫Stringable?的接口,我們用這個接口把“業(yè)務(wù)類型”?Country?和?City?和“控制邏輯”?Print()?給解耦了。于是,只要實現(xiàn)了Stringable?接口,都可以傳給?PrintStr()?來使用。

        這種編程模式在Go 的標(biāo)準(zhǔn)庫有很多的示例,最著名的就是?io.Read?和?ioutil.ReadAll?的玩法,其中?io.Read?是一個接口,你需要實現(xiàn)他的一個?Read(p []byte) (n int, err error)?接口方法,只要滿足這個規(guī)模,就可以被?ioutil.ReadAll這個方法所使用。這就是面向?qū)ο缶幊谭椒ǖ狞S金法則——“Program to an interface not an implementation”


        接口完整性檢查

        另外,我們可以看到,Go語言的編程器并沒有嚴(yán)格檢查一個對象是否實現(xiàn)了某接口所有的接口方法,如下面這個示例:

        type?Shape?interface{???Sides()int???Area()int}type?Square?struct{???len?int}func(s*?Square)Sides()int{???return4}funcmain(){???s?:=?Square{len:?5}???fmt.Printf("%d\n",s.Sides())}

        我們可以看到?Square?并沒有實現(xiàn)?Shape?接口的所有方法,程序雖然可以跑通,但是這樣編程的方式并不嚴(yán)謹(jǐn),如果我們需要強(qiáng)制實現(xiàn)接口的所有方法,那么我們應(yīng)該怎么辦呢?

        在Go語言編程圈里有一個比較標(biāo)準(zhǔn)的作法:

        var _ Shape = (*Square)(nil)

        聲明一個?_?變量(沒人用),其會把一個?nil?的空指針,從?Square?轉(zhuǎn)成?Shape,這樣,如果沒有實現(xiàn)完相關(guān)的接口方法,編譯器就會報錯:

        cannot use (*Square)(nil) (type *Square) as type Shape in assignment: *Square does not implement Shape (missing Area method)

        這樣就做到了個強(qiáng)驗證的方法。


        時間

        對于時間來說,這應(yīng)該是編程中比較復(fù)雜的問題了,相信我,時間是一種非常復(fù)雜的事(比如《你確信你了解時間嗎?》、《關(guān)于閏秒》等文章)。而且,時間有時區(qū)、格式、精度等等問題,其復(fù)雜度不是一般人能處理的。所以,一定要重用已有的時間處理,而不是自己干。

        在 Go 語言中,你一定要使用?time.Time?和?time.Duration?兩個類型:

        • 在命令行上,flag?通過?time.ParseDuration?支持了?time.Duration
        • JSon 中的?encoding/json?中也可以把time.Time?編碼成?RFC 3339?的格式
        • 數(shù)據(jù)庫使用的?database/sql?也支持把?DATATIME?或?TIMESTAMP?類型轉(zhuǎn)成?time.Time
        • YAML你可以使用?gopkg.in/yaml.v2?也支持?time.Time?、time.Duration?和?RFC 3339?格式

        如果你要和第三方交互,實在沒有辦法,也請使用?RFC 3339?的格式。

        最后,如果你要做全球化跨時區(qū)的應(yīng)用,你一定要把所有服務(wù)器和時間全部使用UTC時間。


        性能提示

        Go 語言是一個高性能的語言,但并不是說這樣我們就不用關(guān)心性能了,我們還是需要關(guān)心的。下面是一個在編程方面和性能相關(guān)的提示。

        • 如果需要把數(shù)字轉(zhuǎn)字符串,使用?strconv.Itoa()?會比?fmt.Sprintf()?要快一倍左右
        • 盡可能地避免把String轉(zhuǎn)成[]Byte?。這個轉(zhuǎn)換會導(dǎo)致性能下降。
        • 如果在for-loop里對某個slice 使用?append()請先把 slice的容量很擴(kuò)充到位,這樣可以避免內(nèi)存重新分享以及系統(tǒng)自動按2的N次方冪進(jìn)行擴(kuò)展但又用不到,從而浪費(fèi)內(nèi)存。
        • 使用StringBuffer?或是StringBuild?來拼接字符串,會比使用?+?或?+=?性能高三到四個數(shù)量級。
        • 盡可能的使用并發(fā)的 go routine,然后使用?sync.WaitGroup?來同步分片操作
        • 避免在熱代碼中進(jìn)行內(nèi)存分配,這樣會導(dǎo)致gc很忙。盡可能的使用?sync.Pool?來重用對象。
        • 使用 lock-free的操作,避免使用 mutex,盡可能使用?sync/Atomic包。(關(guān)于無鎖編程的相關(guān)話題,可參看《無鎖隊列實現(xiàn)》或《無鎖Hashmap實現(xiàn)》)
        • 使用 I/O緩沖,I/O是個非常非常慢的操作,使用?bufio.NewWrite()?和?bufio.NewReader()?可以帶來更高的性能。
        • 對于在for-loop里的固定的正則表達(dá)式,一定要使用?regexp.Compile()?編譯正則表達(dá)式。性能會得升兩個數(shù)量級。
        • 如果你需要更高性能的協(xié)議,你要考慮使用?protobuf?或?msgp?而不是JSON,因為JSON的序列化和反序列化里使用了反射。
        • 你在使用map的時候,使用整型的key會比字符串的要快,因為整型比較比字符串比較要快。

        參考文檔

        還有很多不錯的技巧,下面的這些參考文檔可以讓你寫出更好的Go的代碼,必讀!

        • Effective?Go
          https://golang.org/doc/effective_go.html
        • Uber?Go?Style
          https://github.com/uber-go/guide/blob/master/style.md
        • 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
          http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/
        • Go?Advice
          https://github.com/cristaloleg/go-advice
        • Practical Go Benchmarks
          https://www.instana.com/blog/practical-golang-benchmarks/
        • Benchmarks of Go serialization methods
          https://github.com/alecthomas/go_serialization_benchmarks
        • Debugging?performance?issues?in?Go?programs
          https://github.com/golang/go/wiki/Performance
        • Go?code?refactoring:?the?23x?performance?hunt
          https://medium.com/@val_deleplace/go-code-refactoring-the-23x-performance-hunt-156746b522f7


        瀏覽 60
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            男人和女人在羞羞视频 | 欧美性视频一区 | 大尺度av在线 | 国产丰满在线 | 黄色短视频内容在线观看免费 | 亚洲一区二区 | 国产无码在线看 | 欧美精品人妻 | www.久久久久久久 | 五月婷婷综合在线 |