<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go編程模式:切片,接口,時(shí)間和性能

          共 9030字,需瀏覽 19分鐘

           ·

          2021-01-29 12:32


          更多奇技淫巧歡迎訂閱博客:https://fuckcloudnative.io

          前言

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

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

          • Go 編程模式:切片,接口,時(shí)間和性能
          • Go 編程模式:錯(cuò)誤處理[2]
          • Go 編程模式:Functional Options[3]
          • Go 編程模式:委托和反轉(zhuǎn)控制[4]
          • Go 編程模式:Map-Reduce[5]
          • Go 編程模式:Go Generation[6]
          • Go 編程模式:修飾器[7]
          • Go 編程模式:Pipeline[8]
          • Go 編程模式:k8s Visitor 模式[9]

          1. Slice

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

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

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

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

          foo?=?make([]int,?5)
          foo[3]?=?42
          foo[4]?=?100

          bar??:=?foo[1:4]
          bar[1]?=?99

          對(duì)于上面這段代碼。

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

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

          接下來,我們?cè)賮砜匆粋€(gè)數(shù)據(jù)操作 append() 的示例

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

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

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

          我們?cè)倏磥砜匆粋€(gè)例子:

          func?main()?{
          ????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
          }

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

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

          dir1?:=?path[:sepIndex]

          修改為

          dir1?:=?path[:sepIndex:sepIndex]

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

          2. 深度比較

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

          import?(
          ????"fmt"
          ????"reflect"
          )

          func?main()?{

          ????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
          }

          3. 接口編程

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

          func?PrintPerson(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)
          }

          func?main()?{
          ????var?p?=?Person{
          ????????Name:?"Hao?Chen",
          ????????Sexual:?"Male",
          ????????Age:?44,
          ????}

          ????PrintPerson(&p)
          ????p.Print()
          }

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

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

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

          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()

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

          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
          }

          func?PrintStr(p?Stringable)?{
          ????fmt.Println(p.ToString())
          }

          d1?:=?Country?{"USA"}
          d2?:=?City{"Los?Angeles"}
          PrintStr(d1)
          PrintStr(d2)

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

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

          4. 接口完整性檢查

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

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

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

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

          var?_?Shape?=?(*Square)(nil)

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

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

          這樣就做到了個(gè)強(qiáng)驗(yàn)證的方法。

          5. 時(shí)間

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

          在 Go 語言中,你一定要使用 time.Timetime.Duration 兩個(gè)類型:

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

          如果你要和第三方交互,實(shí)在沒有辦法,也請(qǐng)使用 RFC 3339[15] 的格式。

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

          6. 性能提示

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

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

          參考

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

          • Effective Go[20]
          • Uber Go Style[21]
          • 50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs[22]
          • Go Advice[23]
          • Practical Go Benchmarks[24]
          • Benchmarks of Go serialization methods[25]
          • Debugging performance issues in Go programs[26]
          • Go code refactoring: the 23x performance hunt[27]

          參考資料

          [1]

          Go 編程模式: https://coolshell.cn/articles/series/go編程模式

          [2]

          Go 編程模式:錯(cuò)誤處理: https://coolshell.cn/articles/21140.html

          [3]

          Go 編程模式:Functional Options: https://coolshell.cn/articles/21146.html

          [4]

          Go 編程模式:委托和反轉(zhuǎn)控制: https://coolshell.cn/articles/21214.html

          [5]

          Go 編程模式:Map-Reduce: https://coolshell.cn/articles/21164.html

          [6]

          Go 編程模式:Go Generation: https://coolshell.cn/articles/21179.html

          [7]

          Go 編程模式:修飾器: https://coolshell.cn/articles/17929.html

          [8]

          Go 編程模式:Pipeline: https://coolshell.cn/articles/21228.html

          [9]

          Go 編程模式:k8s Visitor 模式: https://coolshell.cn/articles/21263.html

          [10]

          Go 語言簡(jiǎn)介(上):接口與多態(tài): https://coolshell.cn/articles/8460.html#接口和多態(tài)

          [11]

          你確信你了解時(shí)間嗎?: https://coolshell.cn/articles/5075.html

          [12]

          關(guān)于閏秒: https://coolshell.cn/articles/7804.html

          [13]

          RFC 3339: https://tools.ietf.org/html/rfc3339

          [14]

          RFC 3339: https://tools.ietf.org/html/rfc3339

          [15]

          RFC 3339: https://tools.ietf.org/html/rfc3339

          [16]

          無鎖隊(duì)列實(shí)現(xiàn): https://coolshell.cn/articles/8239.html

          [17]

          無鎖 Hashmap 實(shí)現(xiàn): https://coolshell.cn/articles/9703.html

          [18]

          protobuf: https://github.com/golang/protobuf

          [19]

          msgp: https://github.com/tinylib/msgp

          [20]

          Effective Go: https://golang.org/doc/effective_go.html

          [21]

          Uber Go Style: https://github.com/uber-go/guide/blob/master/style.md

          [22]

          50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

          [23]

          Go Advice: https://github.com/cristaloleg/go-advice

          [24]

          Practical Go Benchmarks: https://www.instana.com/blog/practical-golang-benchmarks/

          [25]

          Benchmarks of Go serialization methods: https://github.com/alecthomas/go_serialization_benchmarks

          [26]

          Debugging performance issues in Go programs: https://github.com/golang/go/wiki/Performance

          [27]

          Go code refactoring: the 23x performance hunt: https://medium.com/@val_deleplace/go-code-refactoring-the-23x-performance-hunt-156746b522f7


          原文鏈接:https://coolshell.cn/articles/21128.html



          你可能還喜歡

          點(diǎn)擊下方圖片即可閱讀

          Kuberntes 系統(tǒng)下的 `rm -rf /`,執(zhí)行完就可以跑路了

          云原生是一種信仰???



          碼關(guān)注公眾號(hào)

          后臺(tái)回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!



          點(diǎn)擊?"閱讀原文"?獲取更好的閱讀體驗(yàn)!

          ??給個(gè)「在看」,是對(duì)我最大的支持??
          瀏覽 60
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久伊人免费 | 精品人妻中文字幕 | 欧美靠逼网站 | 99色在线| 中文字幕99 |