空接口
// 空接口类型结构体
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
中的方法列表。
非空接口
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 // 方法列表
}
举例
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快速定位到方法,而不需要去类型元数据那里查找。
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后放回哈希表中。