深入Golang的接口interface
前言
go不要求類型顯示地聲明實現了哪個接口,只要實現了相關的方法即可,編譯器就能檢測到
空接口類型可以接收任意類型的數據:
type eface struct {
// _type 指向接口的動態(tài)類型元數據
// 描述了實體類型、包括內存對齊方式、大小等
_type *_type
// data 指向接口的動態(tài)值
data unsafe.Pointer
}空接口在賦值時,_type 和 data 都是nil。賦值后,_type 會指向賦值的數據元類型,data 會指向該值
非空接口是有方法列表的接口類型,一個變量要賦值給非空接口,就要實現該接口里的所有方法
type iface struct {
// tab 接口表指針,指向一個itab實體,存儲方法列表和接口動態(tài)類型信息
tab *itab
// data 指向接口的動態(tài)值(一般是指向堆內存的)
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 從動態(tài)類型元數據中拷貝的hash值,用于快速判斷類型是否相等
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
// fun 記錄動態(tài)類型實現的那些接口方法的地址
// 存儲的是第一個方法的函數指針
// 這些方法是按照函數名稱的字典序進行排列的
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表用于查出和查詢緩存信息<接口類型, 動態(tài)類型>
如果itab緩存中有,可以直接拿來使用,如果沒有,則新創(chuàng)建一個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和動態(tài)類型hash進行異或
return uintptr(inter.typ.hash ^ typ.hash)
}接口類型和nil作比較
接口值的零值指接口動態(tài)類型和動態(tài)值都為nil,當且僅當此時接口值==nil
如何打印出接口的動態(tài)類型和值?
定義一個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檢測類型是否實現了接口:
賦值語句會發(fā)生隱式的類型轉換,在轉換過程中,編譯器會檢測等號右邊的類型是否實現了等號左邊接口所規(guī)定的函數
// 檢查 *myWriter 類型是否實現了 io.Writer 接口
var _ io.Writer = (*myWriter)(nil)
// 檢查 myWriter 類型是否實現了 io.Writer 接口
var _ io.Writer = myWriter{}接口轉換的原理
將一個接口轉換為另一個接口:
- 實際上是調用了convI2I
- 如果目標是空接口或和目標接口的inter一樣就直接返回
- 否則去獲取itab,然后將值data賦入,再返回
// 接口間的賦值實際上是調用了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并且創(chuàng)建對應的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的方法都按方法名稱進行排序
// 并且方法名是唯一的,因此循環(huán)次數的固定的
// 復雜度為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()
}
// 第二層循環(huán)是從上一次遍歷到的位置開始的
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 表中獲取。
實現多態(tài)
- 多態(tài)的特點
- 一種類型具有多種類型的能力
- 允許不同的對象對同一消息做出靈活的反應
- 以一種通用的方式對待多個使用的對象
- 非動態(tài)語言必須通過繼承和接口的方式實現
實現函數的內部,接口綁定了實體類型,會直接調用fun里保存的函數,類似于s.tab->fun[0],而fun數組中保存的是實體類型實現的函數,當函數傳入不同實體類型時,實際上調用的是不同的函數實現,從而實現了多態(tài)
到此這篇關于深入Golang的接口interface的文章就介紹到這了,更多相關Go 接口 interface內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Golang編程并發(fā)工具庫MapReduce使用實踐
這篇文章主要為大家介紹了Golang并發(fā)工具庫MapReduce的使用實踐,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04

