Go | 类型元数据及类型断言

类型元数据

runtime._type 类型元数据

类型名称、类型大小、对齐边界、是否自定义等,是每个类型元数据都要记录的信息,所以被放到了runtime._type结构体中,作为每个类型元素的Heade

Go | 类型元数据及类型断言

_type之后存储的是各种类型额外需要描述的信息,例如slice的类型元数据在_type结构体后面,记录着一个*_type指向其存储的元素的类型元数据,如果是string类型的slice,这个指针就指向string类型的元数据。

Go | 类型元数据及类型断言

如果是自定义类型,后面还会有一个uncommontype结构体

uncommontype 意义
pkgpath 记录类型所在的包路径
mcount 记录该类型关联到多少个方法
moff 记录的是这些方法元数据组成的数组,相对于这个uncommontype结构体偏移了多少字节

Go | 类型元数据及类型断言

自定义类型元数据

例如我们基于[]string定义一个新类型myslice,他就是一个自定义类型,可以给它定义两个方法LenCap

myslice类型元数据,首先是[]string的类型描述信息,然后在后面加上uncommontype结构体。

注:通过uncommontype这里记录的moff信息,我们就可以找到给myslice定义的方法元数据在哪儿了

如果uncommontype的地址为addrA,加上moff字节的偏移,就是myslice关联的方法元数据数组

Go | 类型元数据及类型断言

利用类型元数据来解释下面两种写法的区别

Go | 类型元数据及类型断言

MyType1这种类型叫做给类型int32取别名,实际上MyType1int32会关联到同一个类型元数据,属于同一种类型,runeint32就是这样的关系。

MyType2这种写法属于基于已有类型创建新类型,MyType2会自立门户,拥有自己的类型元数据,即使MyType2相对于int32来说没有做任何改变,他们两个对应的类型元数据也已经不同了

每种类型都有唯一对应的类型元数据,而类型定义的方法能通过类型元数据找到

接口的数据结构

interface{} 空接口类型

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

Go | 类型元数据及类型断言

  • _type 指向接口的动态类型元数据
  • data 就指向接口的动态值

一个空接口类型变量,再被赋值以前_typedata都为nil

非空接口

非空接口就是有方法列表的接口类型

Go | 类型元数据及类型断言

一个变量要想赋值给一个非空接口类型,必须要实现该接口要求的所有方法才行

Go | 类型元数据及类型断言

  • tab 接口要求的方法列表,以及接口动态类型信息,存在itab结构体中

  • data 就指向接口的动态值

  • inter 指向interface的类型元数据

    • mhdr 接口要求的方法列表
  • _type 指向接口的动态类型元数据

  • hash 是从动态类型元数据中拷贝来的类型哈希值,用于快速判断类型是否相等时使用

  • fun 记录的是这个动态类型实现的那些接口要求的方法地址

Go | 类型元数据及类型断言

此时这个itab结构体中的接口类型inter就是io.ReadWriter
动态类型_type*os.File
注意:itab这里的fun,会从动态类型元数据中拷贝接口要求的那些方法的地址,以便通过rw快速定位到方法,而无需再去类型元数据哪里查找

注:关于itab,一旦接口类型确定了,动态类型也确定了,那么itab的内容就不会改变了,所以itab结构体是可复用的。

itab结构体复用

Go语言会把用到的itab结构体缓存起来,并且以接口和动态类型的组合为key<接口类型,动态类型>,以itab结构体指针为value,构造一个hash表,用于存储与查询itab缓存信息

这里的hash表和map底层的哈希表不同,是一种更为简便的设计,需要一个itab时,会首先去这里查找。

key的哈希值是用类型接口的类型哈希值与动态类型的类型哈希值进行异或运算,如果有对应的itab指针,就直接拿来使用,若itab缓存中没有,就要创建一个itab结构体,然后添加到这个哈希表中

Go | 类型元数据及类型断言

类型断言

类型 ——
抽象类型 空接口,非空接口
具体类型 int、string、slice、map、struct…

类型断言作用在接口值之上.( )
空接口.( ) 非空接口.( )

断言的目标类型可以是具体类型或非空接口类型
.(具体类型) .(非空接口类型)

就组成四种类型断言

空接口.(具体类型)

e.(*os.File)是要判断e的动态类型是否为*os.File,这只需要确定这个_type是否指向*os.File的类型元数据就好

注:Go语言里每种类型的元数据都是全局唯一的

Go | 类型元数据及类型断言

e的动态值就是f,动态类型就是*os.File,如果成功r被复制为e的动态值,如果不成功就会被赋值为*os.File的类型零值nil

非空接口.(具体类型)

rw.(*os.File)是要判断rw的动态类型是否为*os.File,就是经过一次判断,判断&itab是否指向这个itab结构体

Go | 类型元数据及类型断言

注: 程序中用到的itab结构体都会缓存起来,可以通过接口类型和动态类型结合起来的key,查找到对应的itab指针

空接口.(非空接口)

e.(io.ReadWriter)是要判断e的动态类型是否实现了io.ReadWriter接口。e动态值就是f,动态类型就是*os.File

Go | 类型元数据及类型断言

虽然*os.File类型元数据后面可以找到类型关联的方法元数据数组,也不必每次都去检查io.ReadWriter类型元数这里是否有对应接口要求的所有方法。因为有itab缓存,可以先去itab缓存中查找一下,如果没有io.ReadWriter*os.File对应的itab结构体,再去检查*os.File的方法列表。

Go | 类型元数据及类型断言

注意:就算能够从缓存中查找到对应的itab,也要判断itab.fun[0]是否等于0,这是因为断言失败的类型组合其对应的itab结构体也会被缓存起来,只是会把itab.fun[0]置为0,用以标识这里的动态类型并没有实现对应的接口。
这样以后再遇到同种类型断言时就不用再去检查方法列表了,可以直接断言失败

非空接口.(非空接口)

w.(io.ReadWriter)是要判断w存储的动态类型是否实现了io.ReadWriter接口,w时io.Writer类型,接口要求一个Writer方法,io.ReadWriter要求实现ReadWriter两个方法。要确定*os.File是否实现了io.ReadWriter接口,同样会先去itab缓存里查找这个组合对应的itab指针,若存在,且itab.fun[0]不等于0,则断言成功,如不存在,再去检查*os.File的方法列表,并缓存itab信息

Go | 类型元数据及类型断言

总结

类型断言的关键是明确接口的动态类型,以及对应的类型实现了哪些方法,而明确这些的关键还是类型元数据

上一篇:BDC应用


下一篇:ABAP_DEMO篇33 SUM和COLLECT的用法