關(guān)于Go 空結(jié)構(gòu)體的 3 種使用場(chǎng)景
前言:
在 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)象了,肯定是背后必然有什么原因。
1、為什么使用
說(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)獲?。?br />
// 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)型寬度大?。?/strong>
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)了?
2、空結(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ì)占位符的基本訴求,就是占著坑位,滿(mǎn)足基本輸入輸出就好。
但這時(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á)到了占位符的目的。
3、使用場(chǎng)景
了解清楚為什么空結(jié)構(gòu)作為占位符使用的原因后,我們更進(jìn)一步了解其真實(shí)的使用場(chǎng)景有哪些。
主要分為三塊:
- 實(shí)現(xiàn)方法接收者。
- 實(shí)現(xiàn)集合類(lèi)型。
- 實(shí)現(xiàn)空通道。
3.1 實(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)案例。
3.2 實(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),很方便的就是解決了。
3.3 實(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)。
到此這篇關(guān)于關(guān)于Go 空結(jié)構(gòu)體的 3 種使用場(chǎng)景的文章就介紹到這了,更多相關(guān)Go 空結(jié)構(gòu)體的 3 種使用場(chǎng)景內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單留言板的方法
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單留言板的方法,涉及數(shù)據(jù)庫(kù)、模板頁(yè)面元素等留言板相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02golang gorm 計(jì)算字段和獲取sum()值的實(shí)現(xiàn)
這篇文章主要介紹了golang gorm 計(jì)算字段和獲取sum()值的實(shí)現(xiàn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12解決go build不去vendor下查找包的問(wèn)題
這篇文章主要介紹了解決go build不去vendor下查找包的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12golang-gin-mgo高并發(fā)服務(wù)器搭建教程
這篇文章主要介紹了golang-gin-mgo高并發(fā)服務(wù)器搭建教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解
這篇文章主要為大家介紹了Go 數(shù)據(jù)結(jié)構(gòu)之堆排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08