<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
golang的interface是一種satisfied式的。A類只要實現了IA interface定義的方法,A就satisfied了介面IA。更抽象一層,如果某些設計上需要一些更抽象的共性,比如print各型別,這時需要使用reflect機制,reflect實質上就是將interface的實現暴露了一部分給應用程式碼。要理解reflect,需要深入瞭解interface。go的interface是一種隱式的interface,但golang的型別是編譯階段定的,是static的,如:
type MyInt int var i int var j MyInt
雖然MyInt底層就是int,但在編譯器角度看,i的型別是int,j的型別是MyInt,是靜態、不一致的。兩者要賦值必須要進行型別轉換。即使是interface,就語言角度來看也是靜態的。如:
var r io.Reader
不管r後面用什麼來初始化,它的型別總是io.Reader。更進一步,對於空的interface,也是如此。記住go語言型別是靜態這一點,對於理解interface/reflect很重要。看一例:
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這一句,隱含了一個型別轉換,將tty轉成了io.Reader。
作為一門程式語言,對方法的處理一般分為兩種型別:一是將所有方法組織在一個表格裡,靜態地呼叫(C++, java);二是呼叫時動態查詢方法(python, smalltalk, js)。而go語言是兩者的結合:雖然有table,但是是需要在執行時計算的table。如下例:Binary類實現了兩個方法,String()和Get()
type Binary uint64 func (i Binary) String() string { return strconv.Uitob64(i.Get(), 2) } func (i Binary) Get() uint64 { return uint64(i) }
因為它實現了String(),按照golang的隱式方法實現來看,Binary satisfied了Stringer介面。因此它可以賦值: s:=Stringer(b)。以此為例來說明下interface的實現:interface的記憶體組織如圖:
一個interface值由兩個指標組成,第一個指向一個interface table,叫 itable。itable開頭是一些描述型別的元欄位,後面是一串方法。注意這個方法是interface本身的方法,並非其dynamic value(Binary)的方法。即這裡只有String()方法,而沒有Get方法。但這個方法的實現肯定是具體類的方法,這裡就是Binary的方法。
當這個interface無方法時,itable可以省略,直接指向一個type即可。
另一個指標data指向dynamic value的一個拷貝,這裡則是b的一份拷貝。也就是,給interface賦值時,會在堆上分配記憶體,用於存放拷貝的值。
同樣,當值本身只有一個字長時,這個指標也可以省略。
一個interface的初始值是兩個nil。比如,
var w io.Writer
這時,tab和data都是nil。interface是否為nil取決於itable欄位。所以不一定data為nil就是nil,判斷時要額外注意。
這樣,像這樣的程式碼:
switch v := any.(type) { case int: return strconv.Itoa(v) case float: return strconv.Ftoa(v, 'g', -1) }
實際上是any這個interface取了 any. tab->type。
而interface的函數呼叫實際上就變成了:
s.tab->fun[0](s.data)
。第一個引數即自身型別指標。
itable的生成是理解interface的關鍵。
如剛開始處提的,為了支援go語言這種介面間僅通過方法來聯絡的特性,是沒有辦法像C++一樣,在編譯時預先生成一個method table的,只能在執行時生成。因此,自然的,所有的實體型別都必須有一個包含此型別所有方法的“型別描述符”(type description structure);而interface型別也同樣有一個類似的描述符,包含了所有的方法。
這樣,interface賦值時,計算interface物件的itable時,需要對兩種型別的方法列表進行遍歷對比。如後面程式碼所示,這種計算只需要進行一次,而且優化成了O(m+n)。
可見,interface與itable之間的關係不是獨立的,而是與interface具體的value型別有關。即(interface型別, 具體型別)->itable。
var any interface{} // initialized elsewhere s := any.(Stringer) // dynamic conversion for i := 0; i < 100; i++ { fmt.Println(s.String()) }
itable的計算不需要到函數呼叫時進行,只需要在interface賦值時進行即可,如上第2行,不需要在第4行進行。
最後,看一些實現程式碼:
以下是上面圖中的兩個欄位。
type iface struct { tab *itab // 指南itable data unsafe.Pointer // 指向真實資料 }
再看itab的實現:
type itab struct { inter *interfacetype _type *_type link *itab bad int32 unused int32 fun [1]uintptr // variable sized }
可見,它使用一個疑似連結串列的東西,可以猜這是用作hash表的拉鍊。前兩個欄位應該是用來表達具體的interface型別和實際擁有的值的型別的,即一個itable的key。(上文提到的(interface型別, 具體型別) )
type imethod struct { name nameOff ityp typeOff } type interfacetype struct { typ _type pkgpath name mhdr []imethod }
interfacetype如有若干imethod,可以猜想這是表達interface定義的方法資料結構。
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 }
對於_type,可見裡面有gc的東西,應該就是具體的型別了。這裡有個hash欄位,itable實現就是掛在一個全域性的hash table中。hash時用到了這個欄位:
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 }
可見,這裡有個把interface型別與具體型別之間的資訊結合起來做一個hash的過程,這個hash就是上述的itab的儲存地點,itab中的link就是hash中的拉鍊。
回到itab,看取一個itab的邏輯:
如果發生了typeassert或是interface的賦值(強轉),需要臨時計算一個itab。這時會先在hash表中找,找不到才會真實計算。
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 // 找到了前面計算過的itab } } } // 沒有找到,生成一個,並加入到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)
這個hash是個全域性變數:
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排序過的,因此這裡的匹配只需要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 // 找到匹配,將實際型別的方法填入itab的fun } goto nextimethod } } } nextimethod: } h := itabhash(inter, typ) //插入上面的全域性hash m.link = hash[h] atomicstorep(unsafe.Pointer(&hash[h]), unsafe.Pointer(m)) }
到這裡,interface的資料結構的框架。
reflection實質上是將interface背後的實現暴露了一部分給應用程式碼,使應用程式可以使用interface實現的一些內容。只要理解了interface的實現,reflect就好理解了。如reflect.typeof(i)返回interface i的type,Valueof返回value。
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對it145.com的支援。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45