欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文帶你了解Golang中interface的設(shè)計(jì)與實(shí)現(xiàn)

 更新時(shí)間:2023年01月04日 11:28:49   作者:rubys_  
本文就來(lái)詳細(xì)說(shuō)說(shuō)為什么說(shuō)?接口本質(zhì)是一種自定義類(lèi)型,以及這種自定義類(lèi)型是如何構(gòu)建起?go?的?interface?系統(tǒng)的,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

在上一篇文章《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í)是用 ifaceeface 這兩個(gè)結(jié)構(gòu)體來(lái)表示的:

  • iface 表示某一個(gè)具體的接口(含有方法的接口)。
  • eface 表示一個(gè)空接口(interface{}

iface 和 eface 結(jié)構(gòu)體

ifaceeface 的結(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
}

在上面代碼中,efceface 類(lèi)型的變量,對(duì)應(yīng)到 eface 結(jié)構(gòu)體的話,_type 就是 Bird 這個(gè)類(lèi)型本身,而 data 就是 &bird 這個(gè)指針:

類(lèi)似的,ifciface 類(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)體里面所包含的方法這些信息。具體我們可以看 itabinit 方法(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,chanselect 語(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ì)心,然后也去看了 assertI2IassertI2I2 的源碼,就會(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)行初始化的。

itabinit 方法實(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) 里面的 caseRunnable

在這個(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) 里面的 caseFlyable

因?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.panicdottypeEruntime.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)是 ifaceefaceiface 表示我們通過(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ì)上是 ifaceiface 的轉(zhuǎn)換,或者是 efaceiface 的轉(zhuǎn)換,如果沒(méi)有第二個(gè)返回值,那么轉(zhuǎn)換失敗的時(shí)候會(huì)引發(fā) panic。
  • switch i.(type) { case ...} 本質(zhì)上也是 ifaceefaceiface 的轉(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)文章

  • 詳解go如何優(yōu)雅的使用接口與繼承

    詳解go如何優(yōu)雅的使用接口與繼承

    Go語(yǔ)言中的接口和嵌套結(jié)構(gòu)體是兩種重要的代碼設(shè)計(jì)方式,接口定義了一組方法簽名,使得不同的類(lèi)型能夠以相同的方式進(jìn)行交互,本文將給大家介紹go語(yǔ)言如何優(yōu)雅的使用接口與繼承,文中有詳細(xì)的代碼供大家參考,需要的朋友可以參考下
    2024-06-06
  • golang動(dòng)態(tài)創(chuàng)建類(lèi)的示例代碼

    golang動(dòng)態(tài)創(chuàng)建類(lèi)的示例代碼

    這篇文章主要介紹了golang動(dòng)態(tài)創(chuàng)建類(lèi)的實(shí)例代碼,本文通過(guò)實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • Go語(yǔ)言安裝和GoLand2021最全超詳細(xì)安裝教程

    Go語(yǔ)言安裝和GoLand2021最全超詳細(xì)安裝教程

    Go語(yǔ)言和GoLand的關(guān)系好比于java和idea、python和pycharm,因此我們需要先安裝好Go語(yǔ)言后才能安裝GoLand。它的安裝和java,python的安裝大同小異,好了,下面給大家?guī)?lái)了GoLand2021安裝教程,需要的朋友參考下吧
    2021-08-08
  • go語(yǔ)言算法題解二叉樹(shù)的拷貝、鏡像和對(duì)稱(chēng)

    go語(yǔ)言算法題解二叉樹(shù)的拷貝、鏡像和對(duì)稱(chēng)

    這篇文章主要為大家詳細(xì)介紹了go語(yǔ)言算法題解二叉樹(shù)的拷貝、鏡像和對(duì)稱(chēng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-01-01
  • Golang?中實(shí)現(xiàn)?Set的思路詳解

    Golang?中實(shí)現(xiàn)?Set的思路詳解

    本文介紹了Go中兩種set的實(shí)現(xiàn)原理,并在此基礎(chǔ)介紹了對(duì)應(yīng)于它們的兩個(gè)包簡(jiǎn)單使用,本文介紹的非常詳細(xì),需要的朋友參考下吧
    2024-01-01
  • Go語(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接口

    這篇文章主要介紹了Go語(yǔ)言服務(wù)器開(kāi)發(fā)實(shí)現(xiàn)最簡(jiǎn)單HTTP的GET與POST接口,實(shí)例分析了Go語(yǔ)言http包的使用技巧,需要的朋友可以參考下
    2015-02-02
  • Go語(yǔ)言網(wǎng)站使用異步編程和Goroutine提高Web的性能

    Go語(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-01
  • Golang使用Gin實(shí)現(xiàn)文件上傳的示例代碼

    Golang使用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-02
  • Golang常見(jiàn)錯(cuò)誤之值拷貝和for循環(huán)中的單一變量詳解

    Golang常見(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-11
  • Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過(guò)程解析示例

    Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過(guò)程解析示例

    這篇文章主要為大家介紹了Air實(shí)現(xiàn)Go程序?qū)崟r(shí)熱重載使用過(guò)程解析示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04

最新評(píng)論