一文帶你了解Golang中interface的設(shè)計(jì)與實(shí)現(xiàn)
在上一篇文章《go interface 基本用法》中,我們了解了 go 中interface
的一些基本用法,其中提到過(guò)接口本質(zhì)是一種自定義類(lèi)型,本文就來(lái)詳細(xì)說(shuō)說(shuō)為什么說(shuō)接口本質(zhì)是一種自定義類(lèi)型,以及這種自定義類(lèi)型是如何構(gòu)建起 go 的interface
系統(tǒng)的。
本文使用的源碼版本: go 1.19。另外本文中提到的 interface
和 接口
是同一個(gè)東西。
前言
在了解 go interface
的設(shè)計(jì)過(guò)程中,看了不少資料,但是大多數(shù)資料都有生成匯編的操作,但是在我的電腦上指向生成匯編的操作的時(shí)候, 生成的匯編代碼卻不太一樣,所以有很多的東西無(wú)法驗(yàn)證正確性,這部分內(nèi)容不會(huì)出現(xiàn)在本文中。本文只寫(xiě)那些經(jīng)過(guò)本機(jī)驗(yàn)證正確的內(nèi)容,但也不用擔(dān)心,因?yàn)楹w了 go interface
設(shè)計(jì)與實(shí)現(xiàn)的核心部分內(nèi)容,但由于水平有限,所以只能盡可能地傳達(dá)我所知道的關(guān)于 interface
的一切東西。對(duì)于有疑問(wèn)的部分,有興趣的讀者可以自行探索。
如果想詳細(xì)地了解,建議還是去看看 iface.go
,里面有接口實(shí)現(xiàn)的一些關(guān)鍵的細(xì)節(jié)。但是還是有一些東西被隱藏了起來(lái), 導(dǎo)致我們無(wú)法知道我們 go 代碼會(huì)是 iface.go
里面的哪一段代碼實(shí)現(xiàn)的。
接口是什么
接口(interface
)本質(zhì)上是一種結(jié)構(gòu)體。
我們先來(lái)看看下面的代碼:
//?main.go package?main type?Flyable?interface?{ ?Fly() } //?go?tool?compile?-N?-S?-l?main.go func?main()?{ ?var?f1?interface{} ?println(f1)?//?CALL????runtime.printeface(SB) ?var?f2?Flyable ?println(f2)?//?CALL????runtime.printiface(SB) }
我們可以通過(guò) go tool compile -N -S -l main.go
命令來(lái)生成 main.go
的偽匯編代碼,生成的代碼會(huì)很長(zhǎng),下面省略所有跟本文主題無(wú)關(guān)的代碼:
// main.go:10 => println(f1) 0x0029 00041 (main.go:10) CALL runtime.printeface(SB) // main.go:13 => println(f2) 0x004f 00079 (main.go:13) CALL runtime.printiface(SB)
我們從這段匯編代碼中可以看到,我們 println(f1)
實(shí)際上是對(duì) runtime.printeface
的調(diào)用,我們看看這個(gè) printeface
方法:
func?printeface(e?eface)?{ ?print("(",?e._type,?",",?e.data,?")") }
我們看到了,這個(gè) printeface
接收的參數(shù)實(shí)際上是 eface
類(lèi)型,而不是 interface{}
類(lèi)型,我們?cè)賮?lái)看看 println(f2)
實(shí)際調(diào)用的 runtime.printiface
方法:
func?printiface(i?iface)?{ ?print("(",?i.tab,?",",?i.data,?")") }
也就是說(shuō) interface{}
類(lèi)型在底層實(shí)際上是 eface
類(lèi)型,而 Flyable
類(lèi)型在底層實(shí)際上是 iface
類(lèi)型。
這就是本文要講述的內(nèi)容,go 中的接口變量其實(shí)是用 iface
和 eface
這兩個(gè)結(jié)構(gòu)體來(lái)表示的:
iface
表示某一個(gè)具體的接口(含有方法的接口)。eface
表示一個(gè)空接口(interface{}
)
iface 和 eface 結(jié)構(gòu)體
iface
和 eface
的結(jié)構(gòu)體定義(runtime/iface.go
):
//?非空接口(如:io.Reader) type?iface?struct?{ ?tab??*itab??????????//?方法表 ?data?unsafe.Pointer?//?指向變量本身的指針 } //?空接口(interface{}) type?eface?struct?{ ?_type?*_type?????????//?接口變量的類(lèi)型 ?data??unsafe.Pointer?//?指向變量本身的指針 }
go 底層的類(lèi)型信息是使用 _type
結(jié)構(gòu)體來(lái)存儲(chǔ)的。
比如,我們有下面的代碼:
package?main type?Bird?struct?{ ?name?string } func?(b?Bird)?Fly()?{ } type?Flyable?interface?{ ?Fly() } func?main()?{ ?bird?:=?Bird{name:?"b1"} ?var?efc?interface{}?=?bird?//?efc?是?eface ?var?ifc?Flyable?=?bird?//?ifc?是?iface ?println(efc)?//?runtime.printeface ?println(ifc)?//?runtime.printiface }
在上面代碼中,efc
是 eface
類(lèi)型的變量,對(duì)應(yīng)到 eface
結(jié)構(gòu)體的話,_type
就是 Bird
這個(gè)類(lèi)型本身,而 data
就是 &bird
這個(gè)指針:
類(lèi)似的,ifc
是 iface
類(lèi)型的變量,對(duì)應(yīng)到 iface
結(jié)構(gòu)體的話,data
也是 &bird
這個(gè)指針:
_type 是什么
在 go 中,_type
是保存了變量類(lèi)型的元數(shù)據(jù)的結(jié)構(gòu)體,定義如下:
//?_type?是?go?里面所有類(lèi)型的一個(gè)抽象,里面包含?GC、反射、大小等需要的細(xì)節(jié), //?它也決定了?data?如何解釋和操作。 //?里面包含了非常多信息:類(lèi)型的大小、哈希、對(duì)齊及?kind?等信息 type?_type?struct?{ ????size???????uintptr?//?數(shù)據(jù)類(lèi)型共占用空間的大小 ????ptrdata????uintptr?//?含有所有指針類(lèi)型前綴大小 ????hash???????uint32??//?類(lèi)型?hash?值;避免在哈希表中計(jì)算 ????tflag??????tflag???//?額外類(lèi)型信息標(biāo)志 ????align??????uint8???//?該類(lèi)型變量對(duì)齊方式 ????fieldAlign?uint8???//?該類(lèi)型結(jié)構(gòu)體字段對(duì)齊方式 ????kind???????uint8???//?類(lèi)型編號(hào) ????//?用于比較此類(lèi)型對(duì)象的函數(shù) ????equal?func(unsafe.Pointer,?unsafe.Pointer)?bool ????//?gc?相關(guān)數(shù)據(jù) ????gcdata????*byte ????str???????nameOff?//?類(lèi)型名字的偏移 ????ptrToThis?typeOff }
這個(gè) _type
結(jié)構(gòu)體定義大家隨便看看就好了,實(shí)際上,go 底層的類(lèi)型表示也不是上面這個(gè)結(jié)構(gòu)體這么簡(jiǎn)單。
但是,我們需要知道的一點(diǎn)是(與本文有關(guān)的信息),通過(guò) _type
我們可以得到結(jié)構(gòu)體里面所包含的方法這些信息。具體我們可以看 itab
的 init
方法(runtime/iface.go
),我們會(huì)看到如下幾行:
typ?:=?m._type x?:=?typ.uncommon()?//?結(jié)構(gòu)體類(lèi)型 nt?:=?int(x.mcount)???//?實(shí)際類(lèi)型的方法數(shù)量 //?實(shí)際類(lèi)型的方法數(shù)組,數(shù)組元素為?method xmhdr?:=?(*[1?<<?16]method)(add(unsafe.Pointer(x),?uintptr(x.moff)))[:nt:nt]
在底層,go 是通過(guò) _type
里面 uncommon
返回的地址,加上一個(gè)偏移量(x.moff
)來(lái)得到實(shí)際結(jié)構(gòu)體類(lèi)型的方法列表的。
我們可以參考一下下圖想象一下:
itab 是什么
我們從 iface
中可以看到,它包含了一個(gè) *itab
類(lèi)型的字段,我們看看這個(gè) itab
的定義:
//?編譯器已知的?itab?布局 type?itab?struct?{ ?inter?*interfacetype?//?接口類(lèi)型 ?_type?*_type ?hash??uint32 ?_?????[4]byte ?fun???[1]uintptr?//?變長(zhǎng)數(shù)組.?fun[0]==0?意味著?_type?沒(méi)有實(shí)現(xiàn)?inter?這個(gè)接口 } //?接口類(lèi)型 //?對(duì)應(yīng)源代碼:type?xx?interface?{} type?interfacetype?struct?{ ????typ?????_type?????//?類(lèi)型信息 ????pkgpath?name??????//?包路徑 ????mhdr????[]imethod?//?接口的方法列表 }
根據(jù) interfacetype
我們可以得到關(guān)于接口所有方法的信息。同樣的,通過(guò) _type
也可以獲取結(jié)構(gòu)體類(lèi)型的所有方法信息。
從定義上,我們可以看到 itab
跟 *interfacetype
和 *_type
有關(guān),但實(shí)際上有什么關(guān)系從定義上其實(shí)不太能看得出來(lái), 但是我們可以看它是怎么被使用的,現(xiàn)在,假設(shè)我們有如下代碼:
//?i?在底層是一個(gè)?interfacetype?類(lèi)型 type?i?interface?{ ?A() ?C() } //?t?底層會(huì)用?_type?來(lái)表示 //?t?里面有?A、B、C、D?方法 //?因?yàn)閷?shí)現(xiàn)了?i?中的所有方法,所以?t?實(shí)現(xiàn)了接口?i type?t?struct?{} func?(t)?A()??{} func?(t)?B()??{} func?(t)?C()??{} func?(t)?D()??{}
下圖描述了上面代碼對(duì)應(yīng)的 itab
生成的過(guò)程:
說(shuō)明:
itab
里面的inter
是接口類(lèi)型的指針(比如通過(guò)type Reader interface{}
這種形式定義的接口,記錄的是這個(gè)類(lèi)型本身的信息),這個(gè)接口類(lèi)型本身定義了一系列的方法,如圖中的i
包含了A
、C
兩個(gè)方法。_type
是實(shí)際類(lèi)型的指針,記錄的是這個(gè)實(shí)際類(lèi)型本身的信息,比如這個(gè)類(lèi)型包含哪些方法。圖中的i
實(shí)現(xiàn)了A
、B
、C
、D
四個(gè)方法,因?yàn)閷?shí)現(xiàn)了i
的所有方法,所以說(shuō)t
實(shí)現(xiàn)了i
接口。- 在底層做類(lèi)型轉(zhuǎn)換的時(shí)候,比如
t
轉(zhuǎn)換為i
的時(shí)候(var v i = t{}
),會(huì)生成一個(gè)itab
,如果t
沒(méi)有實(shí)現(xiàn)i
中的所有方法,那么生成的itab
中不包含任何方法。 - 如果
t
實(shí)現(xiàn)了i
中的所有方法,那么生成的itab
中包含了i
中的所有方法指針,但是實(shí)際指向的方法是實(shí)際類(lèi)型的方法(也就是指向的是t
中的方法地址) mhdr
就是itab
中的方法表,里面的方法名就是接口的所有方法名,這個(gè)方法表中保存了實(shí)際類(lèi)型(t
)中同名方法的函數(shù)地址,通過(guò)這個(gè)地址就可以調(diào)用實(shí)際類(lèi)型的方法了。
所以,我們有如下結(jié)論:
itab
實(shí)際上定義了interfacetype
和_type
之間方法的交集。作用是什么呢?就是用來(lái)判斷一個(gè)結(jié)構(gòu)體是否實(shí)現(xiàn)某個(gè)接口的。itab
包含了接口的所有方法,這里面的方法是實(shí)際類(lèi)型的子集。itab
里面的方法列表包含了實(shí)際類(lèi)型的方法指針(也就是實(shí)際類(lèi)型的方法的地址),通過(guò)這個(gè)地址可以對(duì)實(shí)際類(lèi)型進(jìn)行方法的調(diào)用。itab
在實(shí)際類(lèi)型沒(méi)有實(shí)現(xiàn)接口的所有方法的時(shí)候,生成失?。ㄊ〉囊馑际?,生成的itab
里面的方法列表是空的,在底層實(shí)現(xiàn)上是用fun[0] = 0
來(lái)表示)。
生成的 itab 是怎么被使用的
go 里面定義了一個(gè)全局變量 itabTable
,用來(lái)緩存 itab
,因?yàn)樵谂袛嗄骋粋€(gè)結(jié)構(gòu)體是否實(shí)現(xiàn)了某一個(gè)接口的時(shí)候, 需要比較兩者的方法集,如果結(jié)構(gòu)體實(shí)現(xiàn)了接口的所有方法,那么就表明結(jié)構(gòu)體實(shí)現(xiàn)了接口(這也就是生成 itab
的過(guò)程)。 如果在每一次做接口斷言的時(shí)候都要做一遍這個(gè)比較,性能無(wú)疑會(huì)大大地降低,因此 go 就把這個(gè)比較得出的結(jié)果緩存起來(lái),也就是 itab
。 這樣在下一次判斷結(jié)構(gòu)體是否實(shí)現(xiàn)了某一個(gè)接口的時(shí)候,就可以直接使用之前的 itab
,性能也就得到提升了。
//?表里面緩存了?itab itabTable?????=?&itabTableInit itabTableInit?=?itabTableType{size:?itabInitSize} //?全局的?itab?表 type?itabTableType?struct?{ ????size????uintptr?????????????//?entries?的長(zhǎng)度,2?的次方 ????count???uintptr?????????????//?當(dāng)前?entries?的數(shù)量 ????entries?[itabInitSize]*itab?//?保存?itab?的哈希表 }
itabTableType
里面的 entries
是一個(gè)哈希表,在實(shí)際保存的時(shí)候,會(huì)用 interfacetype
和 _type
這兩個(gè)生成一個(gè)哈希表的鍵。 也就是說(shuō),這個(gè)保存 itab
的緩存哈希表中,只要我們有 interfacetype
和 _type
這兩個(gè)信息,就可以獲取一個(gè) itab
。
具體怎么使用,我們可以看看下面的例子:
package?main type?Flyable?interface?{ ?Fly() } type?Runnable?interface?{ ?Run() } var?_?Flyable?=?(*Bird)(nil) var?_?Runnable?=?(*Bird)(nil) type?Bird?struct?{ } func?(b?Bird)?Fly()?{ } func?(b?Bird)?Run()?{ } //?GOOS=linux?GOARCH=amd64?go?tool?compile?-N?-S?-l?main.go?>?main.s func?test()?{ ?//?f?的類(lèi)型是?iface ?var?f?Flyable?=?Bird{} ?//?Flyable?轉(zhuǎn)?Runnable?本質(zhì)上是?iface?到?iface?的轉(zhuǎn)換 ?f.(Runnable).Run()?//?CALL?runtime.assertI2I(SB) ?//?這個(gè)?switch?里面的類(lèi)型斷言本質(zhì)上也是?iface?到?iface?的轉(zhuǎn)換 ?//?但是?switch?里面的類(lèi)型斷言失敗不會(huì)引發(fā)?panic ?switch?f.(type)?{ ?case?Flyable:?//?CALL?runtime.assertI2I2(SB) ?case?Runnable:?//?CALL?runtime.assertI2I2(SB) ?} ?if?_,?ok?:=?f.(Runnable);?ok?{?//?CALL?runtime.assertI2I2(SB) ?} ?//?i?的類(lèi)型是?eface ?var?i?interface{}?=?Bird{} ?//?i?轉(zhuǎn)?Flyable?本質(zhì)上是?eface?到?iface?的轉(zhuǎn)換 ?i.(Flyable).Fly()?//?CALL?runtime.assertE2I(SB) ?//?這個(gè)?switch?里面的類(lèi)型斷言本質(zhì)上也是?eface?到?iface?的轉(zhuǎn)換 ?//?但是?switch?里面的類(lèi)型斷言失敗不會(huì)引發(fā)?panic ?switch?i.(type)?{ ?case?Flyable:?//?CALL?runtime.assertE2I2(SB) ?case?Runnable:?//?CALL?runtime.assertE2I2(SB) ?} ?if?_,?ok?:=?i.(Runnable);?ok?{?//?CALL?runtime.assertE2I2(SB) ?} }
我們對(duì)上面的代碼生成偽匯編代碼:
GOOS=linux GOARCH=amd64 go tool compile -N -S -l main.go > main.s
然后我們?nèi)ゲ榭?main.s
,就會(huì)發(fā)現(xiàn)類(lèi)型斷言的代碼,本質(zhì)上是對(duì) runtime.assert*
方法的調(diào)用(assertI2I
、assertI2I2
、assertE2I
、assertE2I2
), 這幾個(gè)方法名都是以 assert
開(kāi)頭的,assert
在編程語(yǔ)言中的含義是,判斷后面的條件是否為 true
,如果 false
則拋出異?;蛘咂渌袛喑绦驁?zhí)行的操作,為 true
則接著執(zhí)行。 這里的用處就是,判斷一個(gè)接口是否能夠轉(zhuǎn)換為另一個(gè)接口或者另一個(gè)類(lèi)型。
但在這里有點(diǎn)不太一樣,這里有兩個(gè)函數(shù)最后有個(gè)數(shù)字 2
的,表明了我們對(duì)接口的類(lèi)型轉(zhuǎn)換會(huì)有兩種情況,我們上面的代碼生成的匯編其實(shí)已經(jīng)很清楚了,一種情況是直接斷言,使用 i.(T)
這種形式,另外一種是在 switch...case
里面使用,。
我們可以看看它們的源碼,看看有什么不一樣:
//?直接根據(jù)?interfacetype/_type?獲取?itab func?assertE2I(inter?*interfacetype,?t?*_type)?*itab?{ ?if?t?==?nil?{ ??//?顯式轉(zhuǎn)換需要非nil接口值。 ??panic(&TypeAssertionError{nil,?nil,?&inter.typ,?""}) ?} ?//?getitab?的第三個(gè)參數(shù)是?false ?//?表示?getiab?獲取不到?itab?的時(shí)候需要?panic ?return?getitab(inter,?t,?false) } //?將?eface?轉(zhuǎn)換為?iface //?因?yàn)?e?包含了?*_type func?assertE2I2(inter?*interfacetype,?e?eface)?(r?iface)?{ ?t?:=?e._type ?if?t?==?nil?{ ??return ?} ?//?getitab?的第三個(gè)參數(shù)是?true ?//?表示?getitab?獲取不到?itab?的時(shí)候不需要?panic ?tab?:=?getitab(inter,?t,?true) ?if?tab?==?nil?{ ??return ?} ?r.tab?=?tab ?r.data?=?e.data ?return }
getitab
的源碼后面會(huì)有。
從上面的代碼可以看到,其實(shí)帶 2
和不帶 2
后綴的關(guān)鍵區(qū)別在于:getitab
的調(diào)用允不允許失敗。 這有點(diǎn)類(lèi)似于 chan
里面的 select
,chan
的 select
語(yǔ)句中讀寫(xiě) chan
不會(huì)阻塞,而其他地方會(huì)阻塞。
assertE2I2
是用在 switch...case
中的,這個(gè)調(diào)用是允許失敗的,因?yàn)槲覀冞€需要判斷能否轉(zhuǎn)換為其他類(lèi)型; 又或者 v, ok := i.(T)
的時(shí)候,也是允許失敗的,但是這種情況會(huì)返回第二個(gè)值給用戶判斷是否轉(zhuǎn)換成功。 而直接使用類(lèi)型斷言的時(shí)候,如 i.(T)
這種,如果 i
不能轉(zhuǎn)換為 T
類(lèi)型,則直接 panic
。
對(duì)于 go 中的接口斷言可以總結(jié)如下:
assertI2I
用于將一個(gè)iface
轉(zhuǎn)換為另一個(gè)iface
zhong,轉(zhuǎn)換失敗的時(shí)候會(huì)panic
assertI2I2
用于將一個(gè)iface
轉(zhuǎn)換為另一個(gè)iface
,轉(zhuǎn)換失敗的時(shí)候不會(huì)panic
assertE2I
用于將一個(gè)eface
轉(zhuǎn)換為另一個(gè)iface
,轉(zhuǎn)換失敗的時(shí)候會(huì)panic
assertE2I2
用于將一個(gè)eface
轉(zhuǎn)換為另一個(gè)iface
,轉(zhuǎn)換失敗的時(shí)候不會(huì)panic
assert
相關(guān)的方法后綴的I2I
、E2E
里面的I
表示的是iface
,E
表示的是eface
- 帶
2
后綴的允許失敗,用于v, ok := i.(T)
或者switch x.(type) ... case
中 - 不帶
2
后綴的不允許失敗,用于i.(T)
這種形式中
當(dāng)然,這里說(shuō)的轉(zhuǎn)換不是說(shuō)直接轉(zhuǎn)換,只是說(shuō),在轉(zhuǎn)換的過(guò)程中會(huì)用到 assert* 方法。
如果我們足夠細(xì)心,然后也去看了 assertI2I
和 assertI2I2
的源碼,就會(huì)發(fā)現(xiàn),這幾個(gè)方法本質(zhì)上都是, 通過(guò) interfacetype
和 _type
來(lái)獲取一個(gè) itab
然后轉(zhuǎn)換為另外一個(gè) itab
或者 `iface。
同時(shí),我們也應(yīng)該注意到,上面的轉(zhuǎn)換都是轉(zhuǎn)換到 iface 而沒(méi)有轉(zhuǎn)換到 eface 的操作,這是因?yàn)椋蓄?lèi)型都可以轉(zhuǎn)換為空接口(interface{},也就是 eface)。根本就不需要斷言。
上面的內(nèi)容可以結(jié)合下圖理解一下:
itab 關(guān)鍵方法的實(shí)現(xiàn)
下面,讓我們?cè)賮?lái)深入了解一下 itab
是怎么被創(chuàng)建出來(lái)的,以及是怎么保存到全局的哈希表中的。我們先來(lái)看看下圖:
這個(gè)圖描述了 go 底層存儲(chǔ) itab
的方式:
- 通過(guò)一個(gè)
itabTableType
類(lèi)型來(lái)存儲(chǔ)所有的itab
。 - 在調(diào)用
getitab
的時(shí)候,會(huì)先根據(jù)inter
和_type
計(jì)算出哈希值,然后從entries
中查找是否存在,存在就返回對(duì)應(yīng)的itab
,不存在則新建一個(gè)itab
。 - 在調(diào)用
itabAdd
的時(shí)候,會(huì)將itab
加入到itabTableType
類(lèi)型變量里面的entries
中,其中entries
里面的鍵是根據(jù)inter
和_type
做哈希運(yùn)算得出的。
itab
兩個(gè)比較關(guān)鍵的方法:
getitab
讓我們可以通過(guò)interfacetype
和_type
獲取一個(gè)itab
,會(huì)現(xiàn)在緩存中找,找不到會(huì)新建一個(gè)。itabAdd
是在我們緩存找不到itab
,然后新建之后,將這個(gè)新建的itab
加入到緩存的方法。
getitab
方法的第三個(gè)參數(shù) canfail
表示當(dāng)前操作是否允許失敗,上面說(shuō)了,如果是用在 switch...case
或者 v, ok := i.(T)
這種是允許失敗的。
//?獲取某一個(gè)類(lèi)型的?itab(從?itabTable?中查找,鍵是?inter?和?_type?的哈希值) //?查找?interfacetype?+?_type?對(duì)應(yīng)的?itab //?找不到就新增。 func?getitab(inter?*interfacetype,?typ?*_type,?canfail?bool)?*itab?{ ?if?len(inter.mhdr)?==?0?{ ??throw("internal?error?-?misuse?of?itab") ?} ?//?不包含?Uncommon?信息的類(lèi)型直接報(bào)錯(cuò) ?if?typ.tflag&tflagUncommon?==?0?{ ??if?canfail?{ ???return?nil ??} ??name?:=?inter.typ.nameOff(inter.mhdr[0].name) ??panic(&TypeAssertionError{nil,?typ,?&inter.typ,?name.name()}) ?} ?//?保存返回的?itab ?var?m?*itab ?//?t?指向了?itabTable(全局的?itab?表) ?t?:=?(*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable))) ?//?會(huì)先從全局?itab?表中查找,找到就直接返回 ?if?m?=?t.find(inter,?typ);?m?!=?nil?{ ??goto?finish ?} ?//?沒(méi)有找到,獲取鎖,再次查找。 ?//?找到則返回 ?lock(&itabLock) ?if?m?=?itabTable.find(inter,?typ);?m?!=?nil?{ ??unlock(&itabLock) ??goto?finish ?} ?//?沒(méi)有在緩存中找到,新建一個(gè)?itab ?m?=?(*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize,?0,?&memstats.other_sys)) ?//?itab?的 ?m.inter?=?inter ?m._type?=?typ ?m.hash?=?0 ?//?itab?初始化 ?m.init() ?//?將新創(chuàng)建的?itab?加入到全局的?itabTable?中 ?itabAdd(m) ?//?釋放鎖 ?unlock(&itabLock) finish: ?//?==?0?表示沒(méi)有任何方法 ?//?下面?!=?0?表示有?inter?和?typ?有方法的交集 ?if?m.fun[0]?!=?0?{ ??return?m ?} ?//?用在?switch?x.(type)?中的時(shí)候,允許失敗而不是直接?panic ?//?但在?x.(Flyable).Fly()?這種場(chǎng)景會(huì)直接?panic ?if?canfail?{ ??return?nil ?} ?//?沒(méi)有找到有方法的交集,panic ?panic(&TypeAssertionError{concrete:?typ,?asserted:?&inter.typ,?missingMethod:?m.init()}) }
itabAdd
將給定的 itab
添加到 itab
哈希表中(itabTable
)。
注意:itabAdd
中在判斷到哈希表的使用量超過(guò) 75%
的時(shí)候,會(huì)進(jìn)行擴(kuò)容,新的容量為舊容量的 2 倍。
//?必須保持?itabLock。 func?itabAdd(m?*itab)?{ ?//?正在分配內(nèi)存的時(shí)候調(diào)用的話報(bào)錯(cuò) ?if?getg().m.mallocing?!=?0?{ ??throw("malloc?deadlock") ?} ?t?:=?itabTable ?//?容量已經(jīng)超過(guò)?75%?的負(fù)載了,hash?表擴(kuò)容 ?if?t.count?>=?3*(t.size/4)?{ ??//?75%?load?factor(實(shí)際上是:t.size?*0.75) ??//?擴(kuò)展哈希表。原來(lái)?2?倍大小。 ??//?我們?nèi)鲋e告訴?malloc?我們需要無(wú)指針內(nèi)存,因?yàn)樗兄赶虻闹刀疾辉诙阎小? ??//?2?是?size?和?count?這兩個(gè)字段需要的空間 ??t2?:=?(*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize,?nil,?true)) ??t2.size?=?t.size?*?2 ??//?復(fù)制條目。 ??//?注意:在復(fù)制時(shí),其他線程可能會(huì)查找itab,但找不到它。 ??//?沒(méi)關(guān)系,然后它們會(huì)嘗試獲取itab鎖,因此等待復(fù)制完成。 ??iterate_itabs(t2.add)????//?遍歷舊的?hash?表,復(fù)制函數(shù)指針到?t2?中 ??if?t2.count?!=?t.count?{?//?復(fù)制出錯(cuò) ???throw("mismatched?count?during?itab?table?copy") ??} ??//?發(fā)布新哈希表。使用原子寫(xiě)入:請(qǐng)參見(jiàn)?getitab?中的注釋。 ??//?使用?t2?覆蓋?itabTable ??atomicstorep(unsafe.Pointer(&itabTable),?unsafe.Pointer(t2)) ??//?使用新的?hash?表 ??//?因?yàn)?t?是局部變量,指向舊的地址, ??//?但是擴(kuò)容之后是新的地址了,所以現(xiàn)在需要將新的地址賦給?t ??t?=?itabTable ??//?注:舊的哈希表可以在此處進(jìn)行GC。 ?} ?//?將?itab?加入到全局哈希表 ?t.add(m) }
其實(shí) itabAdd
的關(guān)鍵路徑比較清晰,只是因?yàn)樗且粋€(gè)哈希表,所以里面在判斷到當(dāng)前 itab
的數(shù)量超過(guò) itabTable
容量的 75%
的時(shí)候,會(huì)對(duì) itabTable
進(jìn)行 2 倍擴(kuò)容。
根據(jù) interfacetype 和 _type 初始化 itab
上面那個(gè)圖我們說(shuō)過(guò),itab
本質(zhì)上是 interfacetype
和 _type
方法的交集,這一節(jié)我們就來(lái)看看,itab
是怎么根據(jù)這兩個(gè)類(lèi)型來(lái)進(jìn)行初始化的。
itab
的 init
方法實(shí)現(xiàn):
//?init?用?m.inter/m._type?對(duì)的所有代碼指針填充?m.fun?數(shù)組。 //?如果該類(lèi)型不實(shí)現(xiàn)接口,它將?m.fun[0]?設(shè)置為?0?,并返回缺少的接口函數(shù)的名稱(chēng)。 //?可以在同一個(gè)m上多次調(diào)用,甚至同時(shí)調(diào)用。 func?(m?*itab)?init()?string?{ ?inter?:=?m.inter????//?接口 ?typ?:=?m._type??????//?實(shí)際的類(lèi)型 ?x?:=?typ.uncommon() ?//?inter?和?typ?都具有按名稱(chēng)排序的方法,并且接口名稱(chēng)是唯一的,因此可以在鎖定步驟中迭代這兩個(gè); ?//?循環(huán)時(shí)間復(fù)雜度是?O(ni+nt),不是?O(ni*nt) ?ni?:=?len(inter.mhdr)?//?接口的方法數(shù)量 ?nt?:=?int(x.mcount)???//?實(shí)際類(lèi)型的方法數(shù)量 ?//?實(shí)際類(lèi)型的方法數(shù)組,數(shù)組元素為?method ?xmhdr?:=?(*[1?<<?16]method)(add(unsafe.Pointer(x),?uintptr(x.moff)))[:nt:nt]?//?大小無(wú)關(guān)緊要,因?yàn)橄旅娴闹羔樤L問(wèn)不會(huì)超出范圍 ?j?:=?0 ?//?用來(lái)保存?inter/_type?對(duì)方法列表的數(shù)組,數(shù)組元素為?unsafe.Pointer(是實(shí)際類(lèi)型方法的指針) ?methods?:=?(*[1?<<?16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]?//?保存?itab?方法的數(shù)組 ?//?第一個(gè)方法的指針 ?var?fun0?unsafe.Pointer imethods: ?for?k?:=?0;?k?<?ni;?k++?{?//?接口方法遍歷 ??i?:=?&inter.mhdr[k]????????????????//?i?是接口方法,?imethod?類(lèi)型 ??itype?:=?inter.typ.typeOff(i.ityp)?//?接口的方法類(lèi)型 ??name?:=?inter.typ.nameOff(i.name)??//?接口的方法名稱(chēng) ??iname?:=?name.name()???????????????//?接口的方法名 ??ipkg?:=?name.pkgPath()?????????????//?接口的包路徑 ??if?ipkg?==?""?{ ???ipkg?=?inter.pkgpath.name() ??} ??//?根據(jù)接口方法查找實(shí)際類(lèi)型的方法 ??for?;?j?<?nt;?j++?{?//?實(shí)際類(lèi)型的方法遍歷 ???t?:=?&xmhdr[j]???????????????//?t?是實(shí)際類(lèi)型的方法,method?類(lèi)型 ???tname?:=?typ.nameOff(t.name)?//?實(shí)際類(lèi)型的方法名 ???//?比較接口的方法跟實(shí)際類(lèi)型的方法是否一致 ???if?typ.typeOff(t.mtyp)?==?itype?&&?tname.name()?==?iname?{ ????//?實(shí)際類(lèi)型的包路徑 ????pkgPath?:=?tname.pkgPath() ????if?pkgPath?==?""?{ ?????pkgPath?=?typ.nameOff(x.pkgpath).name() ????} ????//?如果是導(dǎo)出的方法 ????//?則保存到?itab?中 ????if?tname.isExported()?||?pkgPath?==?ipkg?{ ?????if?m?!=?nil?{ ??????ifn?:=?typ.textOff(t.ifn)?//?實(shí)際類(lèi)型的方法指針(通過(guò)這個(gè)指針可以調(diào)用實(shí)際類(lèi)型的方法) ??????if?k?==?0?{ ???????//?第一個(gè)方法 ???????fun0?=?ifn?//?we'll?set?m.fun[0]?at?the?end ??????}?else?{ ???????methods[k]?=?ifn ??????} ?????} ?????//?比較下一個(gè)方法 ?????continue?imethods ????} ???} ??} ??//?沒(méi)有實(shí)現(xiàn)接口(實(shí)際類(lèi)型沒(méi)有實(shí)現(xiàn)?interface?中的任何一個(gè)方法) ??m.fun[0]?=?0 ??return?iname?//?返回缺失的方法名,返回值在類(lèi)型斷言失敗的時(shí)候會(huì)需要提示用戶 ?} ?//?實(shí)現(xiàn)了接口 ?m.fun[0]?=?uintptr(fun0) ?return?"" }
接口斷言過(guò)程總覽(類(lèi)型轉(zhuǎn)換的關(guān)鍵)
具體來(lái)說(shuō)有四種情況,對(duì)應(yīng)上面提到的 runtime.assert*
方法:
- 實(shí)際類(lèi)型轉(zhuǎn)換到
iface
iface
轉(zhuǎn)換到另一個(gè)iface
- 實(shí)際類(lèi)型轉(zhuǎn)換到
eface
eface
轉(zhuǎn)換到iface
這其中的關(guān)鍵是 interfacetype
+ _type
可以生成一個(gè) itab
。
上面的內(nèi)容可能有點(diǎn)混亂,讓人摸不著頭腦,但是我們通過(guò)上面的講述,相信已經(jīng)了解了 go 接口中底層的一些實(shí)現(xiàn)細(xì)節(jié),現(xiàn)在,就讓我們重新來(lái)捋一下,看看 go 接口到底是怎么實(shí)現(xiàn)的:
首先,希望我們可以達(dá)成的一個(gè)共識(shí)就是,go 的接口斷言本質(zhì)上是類(lèi)型轉(zhuǎn)換,switch...case
里面或 v, ok := i.(T)
允許轉(zhuǎn)換失敗,而 i.(T).xx()
這種不允許轉(zhuǎn)換失敗,轉(zhuǎn)換失敗的時(shí)候會(huì) panic
。
接著,我們就可以通過(guò)下圖來(lái)了解 go 里面的接口整體的實(shí)現(xiàn)原理了(還是以上面的代碼作為例子):
1.將結(jié)構(gòu)體賦值給接口類(lèi)型:var f Flyable = Bird{}
在這個(gè)賦值過(guò)程中,創(chuàng)建了一個(gè) iface
類(lèi)型的變量,這個(gè)變量中的 itab
的方法表只包含了 Flyable
定義的方法。
2.iface
轉(zhuǎn)另一個(gè) iface
:
f.(Runnable)
_, ok := f.(Runnable)
switch f.(type)
里面的case
是Runnable
在這個(gè)斷言過(guò)程中,會(huì)將 Flyable
轉(zhuǎn)換為 Runnable
,本質(zhì)上是一個(gè) iface
轉(zhuǎn)換到另一個(gè) iface
。但是有個(gè)不同之處在于, 兩個(gè) iface
里面的方法列表是不一樣的,只包含了當(dāng)前 interfacetype
里面定義的方法。
3.將結(jié)構(gòu)體賦值給空接口:var i interface{} = Bird{}
在這個(gè)過(guò)程中,創(chuàng)建了一個(gè) eface
類(lèi)型的變量,這個(gè) eface
里面只包含了類(lèi)型信息以及實(shí)際的 Bird
結(jié)構(gòu)體實(shí)例。
4.eface
轉(zhuǎn)換到 iface
i.(Flyable)
_, ok := i.(Runnable)
switch i.(type)
里面的case
是Flyable
因?yàn)?_type
包含了 Bird
類(lèi)型的所有信息,而 data
包含了 Bird
實(shí)例的值,所以這個(gè)轉(zhuǎn)換是可行的。
panicdottypeI 與 panicdottypeE
從前面的幾個(gè)小節(jié),我們知道,go 的 iface
類(lèi)型轉(zhuǎn)換使用的是 runtime.assert*
幾個(gè)方法,還有另外一種情況就是, 在編譯期間編譯器就已經(jīng)知道了無(wú)法轉(zhuǎn)換成功的情況,比如下面的代碼:
package?main type?Flyable?interface?{ ?Fly() } type?Cat?struct?{ } func?(c?Cat)?Fly()?{ } func?(c?Cat)?test()?{ } //?GOOS=linux?GOARCH=amd64?go?tool?compile?-N?-S?-l?main.go?>?main.s func?main()?{ ?var?b?interface{} ?var?_?=?b.(int)?//?CALL?runtime.panicdottypeE(SB) ?var?c?Flyable?=?&Cat{} ?c.(Cat).test()?//?CALL?runtime.panicdottypeI(SB) }
上面的兩個(gè)轉(zhuǎn)換都是錯(cuò)誤的,第一個(gè) b.(int)
嘗試將 nil
轉(zhuǎn)換為 int
類(lèi)型,第二個(gè)嘗試將 *Cat
類(lèi)型轉(zhuǎn)換為 Cat
類(lèi)型, 這兩個(gè)錯(cuò)誤的類(lèi)型轉(zhuǎn)換都在編譯期可以發(fā)現(xiàn),因此它們生成的匯編代碼調(diào)用的是 runtime.panicdottypeE
和 runtime.panicdottypeI
方法:
//?在執(zhí)行?e.(T)?轉(zhuǎn)換時(shí)如果轉(zhuǎn)換失敗,則調(diào)用?panicdottypeE //?have:我們的動(dòng)態(tài)類(lèi)型。 //?want:我們?cè)噲D轉(zhuǎn)換為的靜態(tài)類(lèi)型。 //?iface:我們正在轉(zhuǎn)換的靜態(tài)類(lèi)型。 //?轉(zhuǎn)換的過(guò)程:嘗試將?iface?的?have?轉(zhuǎn)換為?want?失敗了。 //?不是調(diào)用方法的時(shí)候的失敗。 func?panicdottypeE(have,?want,?iface?*_type)?{ ?panic(&TypeAssertionError{iface,?have,?want,?""}) } //?當(dāng)執(zhí)行?i.(T)?轉(zhuǎn)換并且轉(zhuǎn)換失敗時(shí),調(diào)用?panicdottypeI //?跟?panicdottypeE?參數(shù)相同,但是?hava?是動(dòng)態(tài)的?itab?類(lèi)型 func?panicdottypeI(have?*itab,?want,?iface?*_type)?{ ?var?t?*_type ?if?have?!=?nil?{ ??t?=?have._type ?} ?panicdottypeE(t,?want,?iface) }
這兩個(gè)方法都是引發(fā)一個(gè) panic
,因?yàn)槲覀兊念?lèi)型轉(zhuǎn)換失敗了:
iface 和 eface 里面的 data 是怎么來(lái)的
我們先看看下面的代碼:
package?main type?Bird?struct?{ } func?(b?Bird)?Fly()?{ } type?Flyable?interface?{ ?Fly() } //?GOOS=linux?GOARCH=amd64?go?tool?compile?-N?-S?-l?main.go?>?main.s func?main()?{ ?bird?:=?Bird{} ?var?efc?interface{}?=?bird?//?CALL?runtime.convT(SB) ?var?ifc?Flyable?=?bird?????//?CALL?runtime.convT(SB) ?println(efc,?ifc) }
我們生成偽匯編代碼發(fā)現(xiàn),里面將結(jié)構(gòu)體變量賦值給接口類(lèi)型變量的時(shí)候,實(shí)際上是調(diào)用了 convT
方法。
convT* 方法
iface
里面還包含了幾個(gè) conv*
前綴的函數(shù),在我們將某一具體類(lèi)型的值賦值給接口類(lèi)型的時(shí)候,go 底層會(huì)將具體類(lèi)型的值通過(guò) conv*
函數(shù)轉(zhuǎn)換為 iface
里面的 data
指針:
//?convT?將?v?指向的?t?類(lèi)型的值轉(zhuǎn)換為可以用作接口值的第二個(gè)字的指針(接口的第二個(gè)字是指向?data?的指針)。 //?data(Pointer)?=>?指向?interface?第?2?個(gè)字的?Pointer func?convT(t?*_type,?v?unsafe.Pointer)?unsafe.Pointer?{ ?//?...?其他代碼 ?//?分配?_type?類(lèi)型所需要的內(nèi)存 ?x?:=?mallocgc(t.size,?t,?true) ?//?將?v?指向的值復(fù)制到剛剛分配的內(nèi)存上 ?typedmemmove(t,?x,?v) ?return?x }
我們發(fā)現(xiàn),在這個(gè)過(guò)程,實(shí)際上是將值復(fù)制了一份:
iface.go
里面還有將無(wú)符號(hào)值轉(zhuǎn)換為 data
指針的函數(shù),但是還不知道在什么地方會(huì)用到這些方法,如:
//?轉(zhuǎn)換?uint16?類(lèi)型值為?interface?里面?data?的指針。 //?如果是?0~255?的整數(shù),返回指向?staticuint64s?數(shù)組里面對(duì)應(yīng)下標(biāo)的指針。 //?否則,分配新的內(nèi)存地址。 func?convT16(val?uint16)?(x?unsafe.Pointer)?{ ?//?如果小于?256,則使用共享的內(nèi)存地址 ?if?val?<?uint16(len(staticuint64s))?{ ??x?=?unsafe.Pointer(&staticuint64s[val]) ??if?goarch.BigEndian?{ ???x?=?add(x,?6) ??} ?}?else?{ ??//?否則,分配新的內(nèi)存 ??x?=?mallocgc(2,?uint16Type,?false) ??*(*uint16)(x)?=?val ?} ?return }
個(gè)人猜測(cè),僅僅代表個(gè)人猜測(cè),在整數(shù)賦值給 iface
或者 eface
的時(shí)候會(huì)調(diào)用這類(lèi)方法。不管調(diào)不調(diào)用,我們依然可以看看它的設(shè)計(jì),因?yàn)橛行┲档脤W(xué)習(xí)的地方:
staticuint64s
是一個(gè)全局整型數(shù)組,里面存儲(chǔ)的是 0~255
的整數(shù)。上面的代碼可以表示為下圖:
這個(gè)函數(shù)跟上面的 convT
的不同之處在于,它在判斷整數(shù)如果小于 256
的時(shí)候,則使用的是 staticuint64s
數(shù)組里面對(duì)應(yīng)下標(biāo)的地址。 為什么這樣做呢?本質(zhì)上是為了節(jié)省內(nèi)存,因?yàn)閷?duì)于數(shù)字來(lái)說(shuō),其實(shí)除了值本身,沒(méi)有包含其他的信息了,所以如果對(duì)于每一個(gè)整數(shù)都分配新的內(nèi)存來(lái)保存, 無(wú)疑會(huì)造成浪費(fèi)。按 convT16
里面的實(shí)現(xiàn)方式,對(duì)于 0~255
之間的整數(shù),如果需要給它們分配內(nèi)存,就可以使用同一個(gè)指針(指向 staticuint64s[]
數(shù)組中元素的地址)。
這實(shí)際上是享元模式。
Java 里面的小整數(shù)享元模式
go 里使用 staticuint64s
的方式,其實(shí)在 Java 里面也有類(lèi)似的實(shí)現(xiàn),Java 中對(duì)于小整數(shù)也是使用了享元模式, 這樣在裝箱的時(shí)候,就不用分配新的內(nèi)存了,就可以使用共享的一塊內(nèi)存了,當(dāng)然,某一個(gè)整數(shù)能節(jié)省的內(nèi)存非常有限,如果需要分配內(nèi)存的小整數(shù)非常大,那么節(jié)省下來(lái)的內(nèi)存就非??陀^了。 當(dāng)然,也不只是能節(jié)省內(nèi)存這唯一的優(yōu)點(diǎn),從另一方面說(shuō),它也節(jié)省了垃圾回收器回收內(nèi)存的開(kāi)銷(xiāo),因?yàn)椴恍枰芾砟敲炊鄡?nèi)存。
我們來(lái)看看 Java 中的例子:
class?Test?{ ????public?static?void?main(String[]?args)?{ ????????Integer?k1?=?127; ????????Integer?k2?=?127; ????????System.out.println(k1?==?k2);?//?true ????????System.out.println(k1.equals(k2));?//?true ????????Integer?k10?=?128; ????????Integer?k20?=?128; ????????System.out.println(k10?==?k20);?//?false ????????System.out.println(k10.equals(k20));?//?true ????} }
Java 里面有點(diǎn)不一樣,它是對(duì) -128~127
范圍內(nèi)的整數(shù)做了享元模式的處理,而 go 里面是 0~255
。
上面的代碼中,當(dāng)我們使用 ==
來(lái)比較 Integer
的時(shí)候,值相等的兩個(gè)數(shù),在 -128~127
的范圍的時(shí)候,返回的是 true
,超出這個(gè)范圍的時(shí)候比較返回的是 false
。 這是因?yàn)樵?-128~127
的時(shí)候,值相等的兩個(gè)數(shù)字指向了相同的內(nèi)存地址,超出這個(gè)范圍的時(shí)候,值相等的兩個(gè)數(shù)指向了不同的地址。
Java 的詳細(xì)實(shí)現(xiàn)可以看 java.lang.Integer.IntegerCache
。
總結(jié)
- go 的的接口(
interface
)本質(zhì)上是一種結(jié)構(gòu)體,底冊(cè)實(shí)現(xiàn)是iface
和eface
,iface
表示我們通過(guò)type i interface{}
定義的接口,而eface
表示interface{}/any
,也就是空接口。 iface
里面保存的itab
中保存了具體類(lèi)型的方法指針列表,data
保存了具體類(lèi)型值的內(nèi)存地址。eface
里面保存的_type
包含了具體類(lèi)型的所有信息,data
保存了具體類(lèi)型值的內(nèi)存地址。itab
是底層保存接口類(lèi)型跟具體類(lèi)型方法交集的結(jié)構(gòu)體,如果具體類(lèi)型實(shí)現(xiàn)了接口的所有方法,那么這個(gè)itab
里面的保存有指向具體類(lèi)型方法的指針。如果具體類(lèi)型沒(méi)有實(shí)現(xiàn)接口的全部方法,那么itab
中的不會(huì)保存任何方法的指針(從itab
的作用上看,我們可以看作是一個(gè)空的itab
)。- 不管
itab
的方法列表是否為空,interfacetype
和_type
比較之后生成的itab
會(huì)緩存下來(lái),在后續(xù)比較的時(shí)候可以直接使用緩存。 _type
是 go 底層用來(lái)表示某一個(gè)類(lèi)型的結(jié)構(gòu)體,包含了類(lèi)型所需空間大小等信息。- 類(lèi)型斷言
i.(T)
本質(zhì)上是iface
到iface
的轉(zhuǎn)換,或者是eface
到iface
的轉(zhuǎn)換,如果沒(méi)有第二個(gè)返回值,那么轉(zhuǎn)換失敗的時(shí)候會(huì)引發(fā)panic
。 switch i.(type) { case ...}
本質(zhì)上也是iface
或eface
到iface
的轉(zhuǎn)換,但是轉(zhuǎn)換失敗的時(shí)候不會(huì)引發(fā)panic
。- 全局的保存
itab
的緩存結(jié)構(gòu)體,底層是使用了一個(gè)哈希表來(lái)保存itab
的,在哈希表使用超過(guò)75%
的時(shí)候,會(huì)觸發(fā)擴(kuò)容,新的哈希表容量為舊的2
倍。 staticuint64s
使用了享元模式,Java 中也有類(lèi)似的實(shí)現(xiàn)。
以上就是一文帶你了解Golang中interface的設(shè)計(jì)與實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Golang interface的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang動(dòng)態(tài)創(chuàng)建類(lèi)的示例代碼
這篇文章主要介紹了golang動(dòng)態(tài)創(chuàng)建類(lèi)的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2023-06-06Go語(yǔ)言安裝和GoLand2021最全超詳細(xì)安裝教程
Go語(yǔ)言和GoLand的關(guān)系好比于java和idea、python和pycharm,因此我們需要先安裝好Go語(yǔ)言后才能安裝GoLand。它的安裝和java,python的安裝大同小異,好了,下面給大家?guī)?lái)了GoLand2021安裝教程,需要的朋友參考下吧2021-08-08go語(yǔ)言算法題解二叉樹(shù)的拷貝、鏡像和對(duì)稱(chēng)
這篇文章主要為大家詳細(xì)介紹了go語(yǔ)言算法題解二叉樹(shù)的拷貝、鏡像和對(duì)稱(chēng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-01-01Golang?中實(shí)現(xiàn)?Set的思路詳解
本文介紹了Go中兩種set的實(shí)現(xiàn)原理,并在此基礎(chǔ)介紹了對(duì)應(yīng)于它們的兩個(gè)包簡(jiǎn)單使用,本文介紹的非常詳細(xì),需要的朋友參考下吧2024-01-01Go語(yǔ)言服務(wù)器開(kāi)發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口
這篇文章主要介紹了Go語(yǔ)言服務(wù)器開(kāi)發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口,實(shí)例分析了Go語(yǔ)言http包的使用技巧,需要的朋友可以參考下2015-02-02Go語(yǔ)言網(wǎng)站使用異步編程和Goroutine提高Web的性能
作為一門(mén)現(xiàn)代化編程語(yǔ)言,Go語(yǔ)言提供了強(qiáng)大的異步編程能力,使得程序員可以以更高效的方式處理并發(fā)任務(wù),在Go語(yǔ)言中,使用Goroutine在單個(gè)進(jìn)程中實(shí)現(xiàn)多任務(wù)并行處理,以及如何使用協(xié)程池來(lái)進(jìn)一步提高Web服務(wù)器的處理能力,2024-01-01Golang使用Gin實(shí)現(xiàn)文件上傳的示例代碼
本文我們主要介紹了Golang如何使用Gin實(shí)現(xiàn)文件上傳,Go標(biāo)準(zhǔn)庫(kù)net/http對(duì)文件上傳已經(jīng)提供了非常完善的支持,而Gin框架在其基礎(chǔ)上進(jìn)一步封裝,因此使用Gin開(kāi)發(fā)文件上傳功能時(shí),只需要簡(jiǎn)單幾行代碼便可以實(shí)現(xiàn),需要的朋友可以參考下2024-02-02Golang常見(jiàn)錯(cuò)誤之值拷貝和for循環(huán)中的單一變量詳解
這篇文章主要給大家介紹了關(guān)于Golang常見(jiàn)錯(cuò)誤之值拷貝和for循環(huán)中單一變量的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過(guò)程解析示例
這篇文章主要為大家介紹了Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過(guò)程解析示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04