Golang中的Interface詳解
背景:
golang的interface是一種satisfied式的。A類只要實(shí)現(xiàn)了IA interface定義的方法,A就satisfied了接口IA。更抽象一層,如果某些設(shè)計(jì)上需要一些更抽象的共性,比如print各類型,這時(shí)需要使用reflect機(jī)制,reflect實(shí)質(zhì)上就是將interface的實(shí)現(xiàn)暴露了一部分給應(yīng)用代碼。要理解reflect,需要深入了解interface。go的interface是一種隱式的interface,但golang的類型是編譯階段定的,是static的,如:
type MyInt int var i int var j MyInt
雖然MyInt底層就是int,但在編譯器角度看,i的類型是int,j的類型是MyInt,是靜態(tài)、不一致的。兩者要賦值必須要進(jìn)行類型轉(zhuǎn)換。即使是interface,就語(yǔ)言角度來(lái)看也是靜態(tài)的。如:
var r io.Reader
不管r后面用什么來(lái)初始化,它的類型總是io.Reader。更進(jìn)一步,對(duì)于空的interface,也是如此。記住go語(yǔ)言類型是靜態(tài)這一點(diǎn),對(duì)于理解interface/reflect很重要??匆焕?/p>
var r io.Reader tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) if err != nil { return nil, err } r = tty
到這里,r的類型是什么?r的類型仍然是interface io.Reader,只是r = tty這一句,隱含了一個(gè)類型轉(zhuǎn)換,將tty轉(zhuǎn)成了io.Reader。
interface的實(shí)現(xiàn):
作為一門(mén)編程語(yǔ)言,對(duì)方法的處理一般分為兩種類型:一是將所有方法組織在一個(gè)表格里,靜態(tài)地調(diào)用(C++, java);二是調(diào)用時(shí)動(dòng)態(tài)查找方法(python, smalltalk, js)。而go語(yǔ)言是兩者的結(jié)合:雖然有table,但是是需要在運(yùn)行時(shí)計(jì)算的table。如下例:Binary類實(shí)現(xiàn)了兩個(gè)方法,String()和Get()
type Binary uint64 func (i Binary) String() string { return strconv.Uitob64(i.Get(), 2) } func (i Binary) Get() uint64 { return uint64(i) }
因?yàn)樗鼘?shí)現(xiàn)了String(),按照golang的隱式方法實(shí)現(xiàn)來(lái)看,Binary satisfied了Stringer接口。因此它可以賦值: s:=Stringer(b)。以此為例來(lái)說(shuō)明下interface的實(shí)現(xiàn):interface的內(nèi)存組織如圖:
一個(gè)interface值由兩個(gè)指針組成,第一個(gè)指向一個(gè)interface table,叫 itable。itable開(kāi)頭是一些描述類型的元字段,后面是一串方法。注意這個(gè)方法是interface本身的方法,并非其dynamic value(Binary)的方法。即這里只有String()方法,而沒(méi)有Get方法。但這個(gè)方法的實(shí)現(xiàn)肯定是具體類的方法,這里就是Binary的方法。
當(dāng)這個(gè)interface無(wú)方法時(shí),itable可以省略,直接指向一個(gè)type即可。
另一個(gè)指針data指向dynamic value的一個(gè)拷貝,這里則是b的一份拷貝。也就是,給interface賦值時(shí),會(huì)在堆上分配內(nèi)存,用于存放拷貝的值。
同樣,當(dāng)值本身只有一個(gè)字長(zhǎng)時(shí),這個(gè)指針也可以省略。
一個(gè)interface的初始值是兩個(gè)nil。比如,
var w io.Writer
這時(shí),tab和data都是nil。interface是否為nil取決于itable字段。所以不一定data為nil就是nil,判斷時(shí)要額外注意。
這樣,像這樣的代碼:
switch v := any.(type) { case int: return strconv.Itoa(v) case float: return strconv.Ftoa(v, 'g', -1) }
實(shí)際上是any這個(gè)interface取了 any. tab->type。
而interface的函數(shù)調(diào)用實(shí)際上就變成了:
s.tab->fun[0](s.data)
。第一個(gè)參數(shù)即自身類型指針。
itable的生成:
itable的生成是理解interface的關(guān)鍵。
如剛開(kāi)始處提的,為了支持go語(yǔ)言這種接口間僅通過(guò)方法來(lái)聯(lián)系的特性,是沒(méi)有辦法像C++一樣,在編譯時(shí)預(yù)先生成一個(gè)method table的,只能在運(yùn)行時(shí)生成。因此,自然的,所有的實(shí)體類型都必須有一個(gè)包含此類型所有方法的“類型描述符”(type description structure);而interface類型也同樣有一個(gè)類似的描述符,包含了所有的方法。
這樣,interface賦值時(shí),計(jì)算interface對(duì)象的itable時(shí),需要對(duì)兩種類型的方法列表進(jìn)行遍歷對(duì)比。如后面代碼所示,這種計(jì)算只需要進(jìn)行一次,而且優(yōu)化成了O(m+n)。
可見(jiàn),interface與itable之間的關(guān)系不是獨(dú)立的,而是與interface具體的value類型有關(guān)。即(interface類型, 具體類型)->itable。
var any interface{} // initialized elsewhere s := any.(Stringer) // dynamic conversion for i := 0; i < 100; i++ { fmt.Println(s.String()) }
itable的計(jì)算不需要到函數(shù)調(diào)用時(shí)進(jìn)行,只需要在interface賦值時(shí)進(jìn)行即可,如上第2行,不需要在第4行進(jìn)行。
最后,看一些實(shí)現(xiàn)代碼:
以下是上面圖中的兩個(gè)字段。
type iface struct { tab *itab // 指南itable data unsafe.Pointer // 指向真實(shí)數(shù)據(jù) }
再看itab的實(shí)現(xiàn):
type itab struct { inter *interfacetype _type *_type link *itab bad int32 unused int32 fun [1]uintptr // variable sized }
可見(jiàn),它使用一個(gè)疑似鏈表的東西,可以猜這是用作hash表的拉鏈。前兩個(gè)字段應(yīng)該是用來(lái)表達(dá)具體的interface類型和實(shí)際擁有的值的類型的,即一個(gè)itable的key。(上文提到的(interface類型, 具體類型) )
type imethod struct { name nameOff ityp typeOff } type interfacetype struct { typ _type pkgpath name mhdr []imethod }
interfacetype如有若干imethod,可以猜想這是表達(dá)interface定義的方法數(shù)據(jù)結(jié)構(gòu)。
type _type struct { size uintptr ptrdata uintptr // size of memory prefix holding all pointers hash uint32 tflag tflag align uint8 fieldalign uint8 kind uint8 alg *typeAlg // gcdata stores the GC type data for the garbage collector. // If the KindGCProg bit is set in kind, gcdata is a GC program. // Otherwise it is a ptrmask bitmap. See mbitmap.go for details. gcdata *byte str nameOff ptrToThis typeOff }
對(duì)于_type,可見(jiàn)里面有g(shù)c的東西,應(yīng)該就是具體的類型了。這里有個(gè)hash字段,itable實(shí)現(xiàn)就是掛在一個(gè)全局的hash table中。hash時(shí)用到了這個(gè)字段:
func itabhash(inter *interfacetype, typ *_type) uint32 { // compiler has provided some good hash codes for us. h := inter.typ.hash h += 17 * typ.hash // TODO(rsc): h += 23 * x.mhash ? return h % hashSize }
可見(jiàn),這里有個(gè)把interface類型與具體類型之間的信息結(jié)合起來(lái)做一個(gè)hash的過(guò)程,這個(gè)hash就是上述的itab的存儲(chǔ)地點(diǎn),itab中的link就是hash中的拉鏈。
回到itab,看取一個(gè)itab的邏輯:
如果發(fā)生了typeassert或是interface的賦值(強(qiáng)轉(zhuǎn)),需要臨時(shí)計(jì)算一個(gè)itab。這時(shí)會(huì)先在hash表中找,找不到才會(huì)真實(shí)計(jì)算。
h := itabhash(inter, typ) // look twice - once without lock, once with. // common case will be no lock contention. var m *itab var locked int for locked = 0; locked < 2; locked++ { if locked != 0 { lock(&ifaceLock) } for m = (*itab)(atomic.Loadp(unsafe.Pointer(&hash[h]))); m != nil; m = m.link { if m.inter == inter && m._type == typ { return m // 找到了前面計(jì)算過(guò)的itab } } } // 沒(méi)有找到,生成一個(gè),并加入到itab的hash中。 m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys)) m.inter = inter m._type = typ additab(m, true, canfail)
這個(gè)hash是個(gè)全局變量:
const ( hashSize = 1009 ) var ( ifaceLock mutex // lock for accessing hash hash [hashSize]*itab )
最后,看一下如何生成itab:
// both inter and typ have method sorted by name, // and interface names are unique, // so can iterate over both in lock step; // the loop is O(ni+nt) not O(ni*nt). // 按name排序過(guò)的,因此這里的匹配只需要O(ni+nt) j := 0 for k := 0; k < ni; k++ { i := &inter.mhdr[k] itype := inter.typ.typeOff(i.ityp) name := inter.typ.nameOff(i.name) iname := name.name() for ; j < nt; j++ { t := &xmhdr[j] tname := typ.nameOff(t.name) if typ.typeOff(t.mtyp) == itype && tname.name() == iname { if m != nil { ifn := typ.textOff(t.ifn) *(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn // 找到匹配,將實(shí)際類型的方法填入itab的fun } goto nextimethod } } } nextimethod: } h := itabhash(inter, typ) //插入上面的全局hash m.link = hash[h] atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m)) }
到這里,interface的數(shù)據(jù)結(jié)構(gòu)的框架。
reflection實(shí)質(zhì)上是將interface背后的實(shí)現(xiàn)暴露了一部分給應(yīng)用代碼,使應(yīng)用程序可以使用interface實(shí)現(xiàn)的一些內(nèi)容。只要理解了interface的實(shí)現(xiàn),reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value。
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
- Go語(yǔ)言interface詳解
- golang中interface接口的深度解析
- Golang中interface{}轉(zhuǎn)為數(shù)組的操作
- go 類型轉(zhuǎn)換方式(interface 類型的轉(zhuǎn)換)
- Go語(yǔ)言中你不知道的Interface詳解
- go語(yǔ)言中的interface使用實(shí)例
- Go?interface{}?轉(zhuǎn)切片類型的實(shí)現(xiàn)方法
- golang基礎(chǔ)之Interface接口的使用
- 淺談Golang 嵌套 interface 的賦值問(wèn)題
- Go中interface機(jī)制的實(shí)現(xiàn)
相關(guān)文章
Go語(yǔ)言的結(jié)構(gòu)體還能這么用?看這篇就夠了
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言結(jié)構(gòu)體的各個(gè)知識(shí)點(diǎn),最后還介紹了空結(jié)構(gòu)體的3種妙用。文中的示例代碼講解詳細(xì),希望對(duì)大家有所幫助2023-02-02golang如何部署到服務(wù)器及應(yīng)注意問(wèn)題解析
這篇文章主要為大家介紹了golang如何部署到服務(wù)器及應(yīng)注意問(wèn)題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01goland 實(shí)現(xiàn)自動(dòng)格式化代碼
這篇文章主要介紹了goland 實(shí)現(xiàn)自動(dòng)格式化代碼的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Golang動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)示例
動(dòng)態(tài)數(shù)組能自動(dòng)調(diào)整大小,與靜態(tài)數(shù)組不同,其大小不固定,可根據(jù)需求變化,實(shí)現(xiàn)通常依賴于數(shù)據(jù)結(jié)構(gòu)如鏈表或數(shù)組加額外信息,本文就來(lái)介紹一下Golang動(dòng)態(tài)數(shù)組的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-10-10Golang設(shè)計(jì)模式之生成器模式講解和代碼示例
生成器是一種創(chuàng)建型設(shè)計(jì)模式,使你能夠分步驟創(chuàng)建復(fù)雜對(duì)象,與其他創(chuàng)建型模式不同,生成器不要求產(chǎn)品擁有通用接口,這使得用相同的創(chuàng)建過(guò)程生成不同的產(chǎn)品成為可能,本文就通過(guò)代碼示例為大家詳細(xì)介紹Golang生成器模式,感興趣的同學(xué)可以參考下2023-06-06使用Go語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的Web框架
Go語(yǔ)言(又稱Golang)因其高效的性能和簡(jiǎn)潔的語(yǔ)法,在編寫(xiě)Web框架方面表現(xiàn)出色,下面將詳細(xì)介紹如何使用Go語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的Web框架,文中有詳細(xì)的代碼供大家參考,需要的朋友可以參考下2024-05-05Go語(yǔ)言使用sqlx操作數(shù)據(jù)庫(kù)的示例詳解
sqlx?是?Go?語(yǔ)言中一個(gè)流行的第三方包,它提供了對(duì)?Go?標(biāo)準(zhǔn)庫(kù)?database/sql?的擴(kuò)展,本文重點(diǎn)講解?sqlx?在?database/sql?基礎(chǔ)上擴(kuò)展的功能,希望對(duì)大家有所幫助2023-06-06