reflect
reflect的typeof()有什么用?
reflect的typeof函数是可以动态的获取结构体的名字。
那么是typeof是如何动态的获取结构体的名字?
其获取结构体名字的代码如下:
//main
type sunshine struct{
Light int
}
func main() {
_ :=reflect.TypeOf(sunshine{})
}
//go1.13.4:reflect/value.go
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
首先typeof函数会把自定义的struct赋值给接口,然后对该接口类型转化为一个emptyInterface指针
然后通过emptyInterface指针中的typ获取来获取其中结构体的名字
为什么可以将interface转换为emptyInterface?
原因在于interface接口的内部实现与emptyInterface的实现是一样的,interface具体的内部实现如下:
//go1.13.4:runtime/runtime2.go //go1.13.4:reflect/value.go
//接口的内部实现
//带方法的接口
type iface struct {
tab *itab
data unsafe.Pointer
}
//不带方法的空接口 //emptyInterface的结构
type eface struct { type emptyInterface struct{
_type *_type typ *rtype
data unsafe.Pointer word unsafe.Pointer
} }
其中_type和的rtype具体构造如下:
//go1.13.4:runtime/type.go //go1.13.4:reflect/type.go
type _type struct { type rtype struct {
size uintptr size uintptr
ptrdata uintptr ptrdata uintptr
hash uint32 hash uint32
tflag tflag tflag tflag
align uint8 align uint8
fieldalign uint8 fieldAlign uint8
kind uint8 kind uint8
alg *typeAlg alg *typeAlg
gcdata *byte gcdata *byte
str nameOff str nameOff
ptrToThis typeOff ptrToThis typeOff
} }
由此可见,emptyInterface是通过构造和空接口内部相同的数据结构来进行赋值的。这样reflect包就获取到的空接口内部的所有信息。
而sunshine是如何赋值给interface的呢?
为了对代码进行分析,使用以下代码来进行编译,观察runtime是如何将sunshine结构体赋值给interface的。
type sunshine struct{
Light int64
Name string
}
type inter interface {}
func main(){
var sun sunshine
var i inter
i = sun
fmt.Println(i)
}
其中只观察 i = sun 这一行代码是如何进行编译的(汇编语言:plan9汇编):
#以下为i = sun的部分汇编
...
MOVQ AX, 8(SP)
CALL runtime.convT2E(SB)
PCDATA $0, $1
...
此处的赋值操作是通过调用runtime的convT2E来实现赋值的操作的。而convT2E的实现如下:
//runtime/iface.go
// i = sun 所对应的操作
1 func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
2 if raceenabled {
3 raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
4 }
5 if msanenabled {
6 msanread(elem, t.size)
7 }
8 x := mallocgc(t.size, t, true)
9 typedmemmove(t, x, elem)
10 e._type = t
11 e.data = x
12 return
13}
入参elem:实际上是一个指针指向sun这个变量
入参t:记录关于elem所指向的数据的所有的信息
那么t的数据信息是从哪里的来的?
实际t中的数据信息在编译时就已经构建好的,然后当运行到i = sun时,只需要将信息提取出来放入到t中。如t中的nameOff,align等。
并且在第8行会对elem的数据进行一次拷贝。然后将拷贝后的副本赋值给接口。所以如果当赋值给interface的数据本身很庞大,将会极大的增加gc的负担。
现在我们回过头来看下_type的具体含义:
type _type struct {
size uintptr //数据的大小
ptrdata uintptr //数据中包含指针的大小
hash uint32 //类型的hash值
tflag tflag //一些额外的标识,包含tflagNamed:是否有名字,tflagExtraStar:是否有*号, tflagUncommon:是否包含非一般类型
align uint8 //对齐的字节数
fieldalign uint8 //结构体的对齐字节数
kind uint8 //指向的数据的类型
alg *typeAlg //操作函数
gcdata *byte //垃圾收集器
str nameOff //该_type所对应的数据的名字的偏移
ptrToThis typeOff //该_type所在内存中的偏移
}
所以对应interface的类型对应的数据结构如下:
interface结构 |
---|
type *rtype |
ptr unsafe.Pointer |
其中type记录的ptr指向的数据的相关信息,而ptr则指向对应结构的数据。当对interface进行赋值时会拷贝一份值的数据,然后ptr指向的是其拷贝的数据。
现在回过头来看reflect.TypeOf函数
>typeof函数实际上是返回接口中type中的相关数据,而type相关的数据由于在reflect中定义为私有的。所以只能通过其开放出来的函数进行间接的获取相关的数据。
reflect.ValueOf函数
valueof函数和typeof很相似,实际上是返回是接口的type和ptr数据以及flag。包装在Value中。
下面我们来看下Value.NumField函数:
//reflect/value.go
func (v Value) NumField() int {
v.mustBe(Struct) //用来判断value所对应的值是一个结构体
tt := (*structType)(unsafe.Pointer(v.typ)) //将value中的type强转为structType
return len(tt.fields) //返回tt中fields的个数
}
这里存在一个问题,就是为什么type可以强制转换为structType类型。
原因在于type的内存模型实际上是如下(内存1和内存2和内存3是连续的):
内存 1 | 内存2 | 内存3 |
---|---|---|
rtype | pkgPath | fields |
即当指针为type时,则只会获取到rtype相关的数据,事实上,pkgPath和fields也是在编译时设置好了的。
所以可以直接进行转换。
总结
- 对于接口interface的赋值会拷贝一份数据到interface中,所以如果结构体本身十分的大并且频繁的进行对接口赋值,这会在这中间产生大量的拷贝从而影响性能。
- 而对于反射来获取结构体信息实际上是从runtime中获取结构体的元信息,其实现其实是通过指针加偏移量来找到数据。所以对于获取信息本身不是很消耗性能。
- 由此可见,对于反射性能低的原因主要由以上2点导致的