<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
go不要求型別顯示地宣告實現了哪個介面,只要實現了相關的方法即可,編譯器就能檢測到
空介面型別可以接收任意型別的資料:
type eface struct { // _type 指向介面的動態型別後設資料 // 描述了實體型別、包括記憶體對齊方式、大小等 _type *_type // data 指向介面的動態值 data unsafe.Pointer }
空介面在賦值時,_type 和 data 都是nil。賦值後,_type 會指向賦值的資料元型別,data 會指向該值
非空介面是有方法列表的介面型別,一個變數要賦值給非空介面,就要實現該介面裡的所有方法
type iface struct { // tab 介面表指標,指向一個itab實體,儲存方法列表和介面動態型別資訊 tab *itab // data 指向介面的動態值(一般是指向堆記憶體的) data unsafe.Pointer } // layout of Itab known to compilers // allocated in non-garbage-collected memory // Needs to be in sync with // ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs. type itab struct { // inter 指向interface的型別後設資料,描述了介面的型別 inter *interfacetype // _type 描述了實體型別、包括記憶體對齊方式、大小等 _type *_type // hash 從動態型別後設資料中拷貝的hash值,用於快速判斷型別是否相等 hash uint32 // copy of _type.hash. Used for type switches. _ [4]byte // fun 記錄動態型別實現的那些介面方法的地址 // 儲存的是第一個方法的函數指標 // 這些方法是按照函數名稱的字典序進行排列的 fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. } // interfacetype 介面的型別後設資料 type interfacetype struct { typ _type // 記錄定義了介面的包名 pkgpath name // mhdr 標識介面所定義的函數列表 mhdr []imethod }
itab是可複用的,go會將itab快取起來,構造一個hash表用於查出和查詢快取資訊<介面型別, 動態型別>
如果itab快取中有,可以直接拿來使用,如果沒有,則新建立一個itab,並放入快取中
一個Iface中的具體型別中實現的方法會被拷貝到Itab的fun陣列中
// Note: change the formula in the mallocgc call in itabAdd if you change these fields. type itabTableType struct { size uintptr // length of entries array. Always a power of 2. count uintptr // current number of filled entries. entries [itabInitSize]*itab // really [size] large } func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. // 用介面型別hash和動態型別hash進行互斥或 return uintptr(inter.typ.hash ^ typ.hash) }
介面型別和nil作比較
介面值的零值指介面動態型別和動態值都為nil,當且僅當此時介面值==nil
如何列印出介面的動態型別和值?
定義一個iface結構體,用兩個指標來描述itab和data,然後將具體遍歷在記憶體中的內容強行解釋為我們定義的iface
type iface struct{ itab, data uintptr } func main() { var a interface{} = nil var b interface{} = (*int)(nil) x := 5 var c interface{} = (*int)(&x) ia := *(*iface)(unsafe.Pointer(&a)) ib := *(*iface)(unsafe.Pointer(&b)) ic := *(*iface)(unsafe.Pointer(&c)) fmt.Println(ia, ib, ic) fmt.Println(*(*int)(unsafe.Pointer(ic.data))) } // 輸出 // {0 0} {17426912 0} {17426912 842350714568} // 5
檢測型別是否實現了介面:
賦值語句會發生隱式的型別轉換,在轉換過程中,編譯器會檢測等號右邊的型別是否實現了等號左邊介面所規定的函數
// 檢查 *myWriter 型別是否實現了 io.Writer 介面 var _ io.Writer = (*myWriter)(nil) // 檢查 myWriter 型別是否實現了 io.Writer 介面 var _ io.Writer = myWriter{}
將一個介面轉換為另一個介面:
// 介面間的賦值實際上是呼叫了runtime.convI2I // 實際上是要找到新interface的tab和data // convI2I returns the new itab to be used for the destination value // when converting a value with itab src to the dst interface. func convI2I(dst *interfacetype, src *itab) *itab { if src == nil { return nil } if src.inter == dst { return src } return getitab(dst, src._type, false) } // 關鍵函數,獲取itab // getitab根據interfacetype和_type去全域性的itab雜湊表中查詢,如果找到了直接返回 // 否則根據inter和typ新生成一個itab,插入到全域性itab雜湊表中 // // 查詢了兩次,第二次上鎖了,目的是可能會寫入hash表,阻塞其餘協程的第二次查詢 func getitab(inter *interfacetype, typ *_type, canfail bool) *itab { // 函數列表為空 if len(inter.mhdr) == 0 { throw("internal error - misuse of itab") } // easy case 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()}) } var m *itab // First, look in the existing table to see if we can find the itab we need. // This is by far the most common case, so do it without locks. // Use atomic to ensure we see any previous writes done by the thread // that updates the itabTable field (with atomic.Storep in itabAdd). // 使用原子性去保證我們能看見在該執行緒之前的任意寫操作 // 確保更新全域性hash表的欄位 t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable))) // 遍歷一次,找到了就返回 if m = t.find(inter, typ); m != nil { goto finish } // 沒找到就上鎖,再試一次 // Not found. Grab the lock and try again. lock(&itabLock) if m = itabTable.find(inter, typ); m != nil { unlock(&itabLock) goto finish } // hash表中沒找到itab就新生成一個itab // Entry doesn't exist yet. Make a new entry & add it. m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys)) m.inter = inter m._type = typ // The hash is used in type switches. However, compiler statically generates itab's // for all interface/type pairs used in switches (which are added to itabTable // in itabsinit). The dynamically-generated itab's never participate in type switches, // and thus the hash is irrelevant. // Note: m.hash is _not_ the hash used for the runtime itabTable hash table. m.hash = 0 m.init() // 加到全域性的hash表中 itabAdd(m) unlock(&itabLock) finish: if m.fun[0] != 0 { return m } if canfail { return nil } // this can only happen if the conversion // was already done once using the , ok form // and we have a cached negative result. // The cached result doesn't record which // interface function was missing, so initialize // the itab again to get the missing function name. panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()}) } // 查詢全域性的hash表,有沒有itab // find finds the given interface/type pair in t. // Returns nil if the given interface/type pair isn't present. func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab { // Implemented using quadratic probing. // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k. // We're guaranteed to hit all table entries using this probe sequence. mask := t.size - 1 // 根據inter,typ算出hash值 h := itabHashFunc(inter, typ) & mask for i := uintptr(1); ; i++ { p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize)) // Use atomic read here so if we see m != nil, we also see // the initializations of the fields of m. // m := *p m := (*itab)(atomic.Loadp(unsafe.Pointer(p))) if m == nil { return nil } // inter和typ指標都相同 if m.inter == inter && m._type == typ { return m } h += i h &= mask } } // 核心函數。填充itab // 檢查_type是否符合interface_type並且建立對應的itab結構體將其放到hash表中 // init fills in the m.fun array with all the code pointers for // the m.inter/m._type pair. If the type does not implement the interface, // it sets m.fun[0] to 0 and returns the name of an interface function that is missing. // It is ok to call this multiple times on the same m, even concurrently. func (m *itab) init() string { inter := m.inter typ := m._type x := typ.uncommon() // 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). // inter和typ的方法都按方法名稱進行排序 // 並且方法名是唯一的,因此迴圈次數的固定的 // 複雜度為O(ni+nt),而不是O(ni*nt) ni := len(inter.mhdr) nt := int(x.mcount) xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt] j := 0 methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni] var fun0 unsafe.Pointer imethods: 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() ipkg := name.pkgPath() if ipkg == "" { ipkg = inter.pkgpath.name() } // 第二層迴圈是從上一次遍歷到的位置開始的 for ; j < nt; j++ { t := &xmhdr[j] tname := typ.nameOff(t.name) // 檢查方法名字是否一致 if typ.typeOff(t.mtyp) == itype && tname.name() == iname { pkgPath := tname.pkgPath() if pkgPath == "" { pkgPath = typ.nameOff(x.pkgpath).name() } if tname.isExported() || pkgPath == ipkg { if m != nil { // 獲取函數地址,放入itab.func陣列中 ifn := typ.textOff(t.ifn) if k == 0 { fun0 = ifn // we'll set m.fun[0] at the end } else { methods[k] = ifn } } continue imethods } } } // didn't find method m.fun[0] = 0 return iname } m.fun[0] = uintptr(fun0) return "" } // 檢查是否需要擴容,並呼叫方法將itab存入 // itabAdd adds the given itab to the itab hash table. // itabLock must be held. func itabAdd(m *itab) { // Bugs can lead to calling this while mallocing is set, // typically because this is called while panicing. // Crash reliably, rather than only when we need to grow // the hash table. if getg().m.mallocing != 0 { throw("malloc deadlock") } t := itabTable // 檢查是否需要擴容 if t.count >= 3*(t.size/4) { // 75% load factor // Grow hash table. // t2 = new(itabTableType) + some additional entries // We lie and tell malloc we want pointer-free memory because // all the pointed-to values are not in the heap. t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true)) t2.size = t.size * 2 // Copy over entries. // Note: while copying, other threads may look for an itab and // fail to find it. That's ok, they will then try to get the itab lock // and as a consequence wait until this copying is complete. iterate_itabs(t2.add) if t2.count != t.count { throw("mismatched count during itab table copy") } // Publish new hash table. Use an atomic write: see comment in getitab. atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2)) // Adopt the new table as our own. t = itabTable // Note: the old table can be GC'ed here. } // 核心函數 t.add(m) } // 核心函數 // add adds the given itab to itab table t. // itabLock must be held. func (t *itabTableType) add(m *itab) { // See comment in find about the probe sequence. // Insert new itab in the first empty spot in the probe sequence. // 在探針序列第一個空白點插入itab mask := t.size - 1 // 計算hash值 h := itabHashFunc(m.inter, m._type) & mask for i := uintptr(1); ; i++ { p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize)) m2 := *p if m2 == m { // itab已被插入 // A given itab may be used in more than one module // and thanks to the way global symbol resolution works, the // pointed-to itab may already have been inserted into the // global 'hash'. return } if m2 == nil { // Use atomic write here so if a reader sees m, it also // sees the correctly initialized fields of m. // NoWB is ok because m is not in heap memory. // *p = m // 使用原子操作確保其餘的goroutine下次查詢的時候可以看到他 atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m)) t.count++ return } h += i h &= mask } } // hash函數,編譯期間提供較好的hash codes func itabHashFunc(inter *interfacetype, typ *_type) uintptr { // compiler has provided some good hash codes for us. return uintptr(inter.typ.hash ^ typ.hash) }
具體型別轉空介面時,_type 欄位直接複製源型別的 _type;呼叫 mallocgc 獲得一塊新記憶體,把值複製進去,data 再指向這塊新記憶體。
具體型別轉非空介面時,入參 tab 是編譯器在編譯階段預先生成好的,新介面 tab 欄位直接指向入參 tab 指向的 itab;呼叫 mallocgc 獲得一塊新記憶體,把值複製進去,data 再指向這塊新記憶體。
而對於介面轉介面,itab 呼叫 getitab 函數獲取。只用生成一次,之後直接從 hash 表中獲取。
實現函數的內部,介面繫結了實體型別,會直接呼叫fun裡儲存的函數,類似於s.tab->fun[0],而fun陣列中儲存的是實體型別實現的函數,當函數傳入不同實體型別時,實際上呼叫的是不同的函數實現,從而實現了多型
到此這篇關於深入Golang的介面interface的文章就介紹到這了,更多相關Go 介面 interface內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援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