<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 空結(jié)構(gòu)體的 3 種使用場(chǎng)景

          共 4704字,需瀏覽 10分鐘

           ·

          2021-07-02 04:45

          大家好,我是煎魚(yú)。

          在 Go 語(yǔ)言中,有一個(gè)比較特殊的類(lèi)型,經(jīng)常會(huì)有剛接觸 Go 的小伙伴問(wèn)到,又或是不理解。

          他就是 Go 里的空結(jié)構(gòu)體(struct)的使用,常常會(huì)有看到有人使用:

          ch := make(chan struct{})

          還清一色的使用結(jié)構(gòu)體,也不用其他類(lèi)型。高度常見(jiàn),也就不是一個(gè)偶發(fā)現(xiàn)象了,肯定是背后必然有什么原因。

          今天煎魚(yú)這篇文章帶大家了解一下為什么要這么用,知其然知其所以然。

          一起愉快地開(kāi)始吸魚(yú)之路。

          為什么使用

          說(shuō)白了,就是希望節(jié)省空間。但,新問(wèn)題又來(lái)了,為什么不能用其他的類(lèi)型來(lái)做?

          這就涉及到在 Go 語(yǔ)言中 ”寬度“ 的概念,寬度描述了一個(gè)類(lèi)型的實(shí)例所占用的存儲(chǔ)空間的字節(jié)數(shù)。

          寬度是一個(gè)類(lèi)型的屬性。在 Go 語(yǔ)言中的每個(gè)值都有一個(gè)類(lèi)型,值的寬度由其類(lèi)型定義,并且總是 8 bits 的倍數(shù)。

          在 Go 語(yǔ)言中我們可以借助 unsafe.Sizeof 方法,來(lái)獲取:

          // Sizeof takes an expression x of any type and returns the size in bytes
          // of a hypothetical variable v as if v was declared via var v = x.
          // The size does not include any memory possibly referenced by x.
          // For instance, if x is a slice, Sizeof returns the size of the slice
          // descriptor, not the size of the memory referenced by the slice.
          // The return value of Sizeof is a Go constant.
          func Sizeof(x ArbitraryType) uintptr

          該方法能夠得到值的寬度,自然而然也就能知道其類(lèi)型對(duì)應(yīng)的寬度是多少了。

          我們對(duì)應(yīng)看看 Go 語(yǔ)言中幾種常見(jiàn)的類(lèi)型寬度大小:

          func main() {
           var a int
           var b string
           var c bool
           var d [3]int32
           var e []string
           var f map[string]bool

           fmt.Println(
            unsafe.Sizeof(a),
            unsafe.Sizeof(b),
            unsafe.Sizeof(c),
            unsafe.Sizeof(d),
            unsafe.Sizeof(e),
            unsafe.Sizeof(f),
           )
          }

          輸出結(jié)果:

          8 16 1 12 24 8

          你可以發(fā)現(xiàn)我們列舉的幾種類(lèi)型,只是單純聲明,我們也啥沒(méi)干,依然占據(jù)一定的寬度。

          如果我們的場(chǎng)景,只是占位符,那怎么辦,系統(tǒng)里的開(kāi)銷(xiāo)就這么白白浪費(fèi)了?

          空結(jié)構(gòu)體的特殊性

          空結(jié)構(gòu)體在各類(lèi)系統(tǒng)中頻繁出現(xiàn)的原因之一,就是需要一個(gè)占位符。而恰恰好,Go 空結(jié)構(gòu)體的寬度是特殊的。

          如下:

          func main() {
           var s struct{}
           fmt.Println(unsafe.Sizeof(s))
          }

          輸出結(jié)果:

          0

          空結(jié)構(gòu)體的寬度是很直接了當(dāng)?shù)?0,即便是變形處理:

          type S struct {
           A struct{}
           B struct{}
          }

          func main() {
           var s S
           fmt.Println(unsafe.Sizeof(s))
          }

          其最終輸出結(jié)果也是 0,完美切合人們對(duì)占位符的基本訴求,就是占著坑位,滿足基本輸入輸出就好。

          但這時(shí)候問(wèn)題又出現(xiàn)了,為什么只有空結(jié)構(gòu)會(huì)有這種特殊待遇,其他類(lèi)型又不行?

          這是 Go 編譯器在內(nèi)存分配時(shí)做的優(yōu)化項(xiàng)

          // base address for all 0-byte allocations
          var zerobase uintptr

          func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
           ...
           if size == 0 {
            return unsafe.Pointer(&zerobase)
           }
          }

          當(dāng)發(fā)現(xiàn) size 為 0 時(shí),會(huì)直接返回變量 zerobase 的引用,該變量是所有 0 字節(jié)的基準(zhǔn)地址,不占據(jù)任何寬度。

          因此空結(jié)構(gòu)體的廣泛使用,是 Go 開(kāi)發(fā)者們借助了這個(gè)小優(yōu)化,達(dá)到了占位符的目的。

          使用場(chǎng)景

          了解清楚為什么空結(jié)構(gòu)作為占位符使用的原因后,我們更進(jìn)一步了解其真實(shí)的使用場(chǎng)景有哪些。

          主要分為三塊:

          • 實(shí)現(xiàn)方法接收者。

          • 實(shí)現(xiàn)集合類(lèi)型。

          • 實(shí)現(xiàn)空通道。

          實(shí)現(xiàn)方法接收者

          在業(yè)務(wù)場(chǎng)景下,我們需要將方法組合起來(lái),代表其是一個(gè) ”分組“ 的,便于后續(xù)拓展和維護(hù)。

          但是如果我們使用:

          type T string

          func (s *T) Call()

          又似乎有點(diǎn)不大友好,因?yàn)樽鳛橐粋€(gè)字符串類(lèi)型,其本身會(huì)占據(jù)定的空間。

          這種時(shí)候我們會(huì)采用空結(jié)構(gòu)體的方式,這樣也便于未來(lái)針對(duì)該類(lèi)型進(jìn)行公共字段等的增加。如下:

          type T struct{}

          func (s *T) Call() {
           fmt.Println("腦子進(jìn)煎魚(yú)了")
          }

          func main() {
           var s T
           s.Call()
          }

          在該場(chǎng)景下,使用空結(jié)構(gòu)體從多維度來(lái)考量是最合適的,易拓展,省空間,最結(jié)構(gòu)化。

          另外你會(huì)發(fā)現(xiàn),其實(shí)你在日常開(kāi)發(fā)中下意識(shí)就已經(jīng)這么做了,你可以理解為設(shè)計(jì)模式和日常生活相結(jié)合的另類(lèi)案例。

          實(shí)現(xiàn)集合類(lèi)型

          在 Go 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)中并沒(méi)有提供集合(Set)的相關(guān)實(shí)現(xiàn),因此一般在代碼中我們圖方便,會(huì)直接用 map 來(lái)替代。

          但有個(gè)問(wèn)題,就是集合類(lèi)型的使用,只需要用到 key(鍵),不需要 value(值)。

          這就是空結(jié)構(gòu)體大戰(zhàn)身手的場(chǎng)景了:

          type Set map[string]struct{}

          func (s Set) Append(k string) {
           s[k] = struct{}{}
          }

          func (s Set) Remove(k string) {
           delete(s, k)
          }

          func (s Set) Exist(k string) bool {
           _, ok := s[k]
           return ok
          }

          func main() {
           set := Set{}
           set.Append("煎魚(yú)")
           set.Append("咸魚(yú)")
           set.Append("蒸魚(yú)")
           set.Remove("煎魚(yú)")

           fmt.Println(set.Exist("煎魚(yú)"))
          }

          空結(jié)構(gòu)體作為占位符,不會(huì)額外增加不必要的內(nèi)存開(kāi)銷(xiāo),很方便的就是解決了。

          實(shí)現(xiàn)空通道

          在 Go channel 的使用場(chǎng)景中,常常會(huì)遇到通知型 channel,其不需要發(fā)送任何數(shù)據(jù),只是用于協(xié)調(diào) Goroutine 的運(yùn)行,用于流轉(zhuǎn)各類(lèi)狀態(tài)或是控制并發(fā)情況。

          如下:

          func main() {
           ch := make(chan struct{})
           go func() {
            time.Sleep(1 * time.Second)
            close(ch)
           }()

           fmt.Println("腦子好像進(jìn)...")
           <-ch
           fmt.Println("煎魚(yú)了!")
          }

          輸出結(jié)果:

          腦子好像進(jìn)...
          煎魚(yú)了!

          該程序會(huì)先輸出 ”腦子好像進(jìn)...“ 后,再睡眠一段時(shí)間再輸出 "煎魚(yú)了!",達(dá)到間斷控制 channel 的效果。

          由于該 channel 使用的是空結(jié)構(gòu)體,因此也不會(huì)帶來(lái)額外的內(nèi)存開(kāi)銷(xiāo)。

          總結(jié)

          在今天這篇文章中,給大家介紹了 Go 語(yǔ)言中幾種常見(jiàn)類(lèi)型的寬度,并且基于開(kāi)頭的問(wèn)題 ”空結(jié)構(gòu)體“ 進(jìn)行了剖析。

          最后分析了在業(yè)內(nèi)代碼最常見(jiàn)的三種模式,進(jìn)入真實(shí)場(chǎng)景。不知道你以前是否有過(guò)類(lèi)似本文的疑惑呢?

          歡迎大家在評(píng)論區(qū)留言和交流:)


          關(guān)注煎魚(yú),吸取他的知識(shí) ??


          你好,我是煎魚(yú)。高一折騰過(guò)前端,參加過(guò)國(guó)賽拿了獎(jiǎng),大學(xué)搞過(guò) PHP。現(xiàn)在整 Go,在公司負(fù)責(zé)微服務(wù)架構(gòu)等相關(guān)工作推進(jìn)和研發(fā)。

          從大學(xué)開(kāi)始靠自己賺生活費(fèi)和學(xué)費(fèi),到出版 Go 暢銷(xiāo)書(shū)《Go 語(yǔ)言編程之旅》,再到獲得 GOP(Go 領(lǐng)域最有觀點(diǎn)專(zhuān)家)榮譽(yù),點(diǎn)擊藍(lán)字查看我的出書(shū)之路

          日常分享高質(zhì)量文章,輸出 Go 面試、工作經(jīng)驗(yàn)、架構(gòu)設(shè)計(jì),加微信拉讀者交流群,記得點(diǎn)贊!

          瀏覽 133
          點(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>
                  日本网站在线 | 国产黄色视频在线观看免费 | 久久18禁 | 自拍偷拍中文字幕 | 日韩一二三四区 |