【幼麟实验室】笔记-接口

290 阅读2分钟

空接口

// 空接口类型结构体
type eface struct {
   _type *_type // 动态类型
   data  unsafe.Pointer // 动态值
}

空接口类型可以接收任意类型的数据,它只要记录这个数据是什么类型,这个数据在哪里即可。

_type指向接口的动态类型元数据,data指向接口的动态值。

举例

var e interface{}
f, _ := os.Open("eggo.txt")
e = f

一个空接口类型的变量e,在被赋值前,_type和data都是nil。现在有一个*os.File类型的变量f,将它赋值给e。因为f本来就是指针类型,所以data就等于f,_type指向*os.File的类型元数据。

*os.File作为自定义类型,除了头部(_type),还有uncommontype信息。通过uncommontype,就可以获取*os.File中的方法列表。

image.png

非空接口

type iface struct {
   tab  *itab
   data unsafe.Pointer
}

非空接口就是有方法列表的接口类型。一个变量想要赋值给非空接口类型,必须要实现该接口要求的所有方法。

与空接口类型一样,data字段也指向接口的动态值,tab字段保存接口要求的方法列表和接口动态类型信息。

type itab struct {
   inter *interfacetype // 指向interface的类型元数据
   _type *_type // 动态类型
   hash  uint32 // 从动态类型元数据中拷贝来的类型哈希值,用于快速判断类型是否相等
   _     [4]byte
   fun   [1]uintptr // 方法地址数组,这个动态类型实现的接口要求的方法的地址
}
// interface的类型元数据
type interfacetype struct {
   typ     _type
   pkgpath name
   mhdr    []imethod // 方法列表
}

image.png

举例

var rw io.ReadWriter
f, _ := os.Open("eggo.txt")
rw = f

声明一个io.ReadWriter类型的接口变量rw,未被赋值前,tab和data都是nil。然后将一个*os.File类型的变量f赋值给rw。

此时rw的动态值(data)就是f,tab会指向一个itab结构体,它的接口类型是io.ReadWriter,动态类型为*os.File

itab这里的fun,会从动态类型元数据中拷贝接口要求的那些方法的地址,以便rw快速定位到方法,而不需要去类型元数据那里查找。

image.png

itab复用

Go语言会把用到的itab结构体缓存起来,并且以接口类型和动态类型组成key,以itab为value,构建一个哈希表。

const itabInitSize = 512

// 更加简洁的哈希表
type itabTableType struct {
   size    uintptr         
   count   uintptr             
   entries [itabInitSize]*itab // *itab类型的数组,默认容量为512
}

// 将接口类型的哈希值和动态类型的哈希值异或
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
   return uintptr(inter.typ.hash ^ typ.hash)
}

当创建非空接口时并对它实现时,会首先从哈希表中查询,如果有,直接返回。如果没有,创建新的itab后放回哈希表中。