之前在golang底层代码和业务代码中经常见到使用反射,感觉对反射的使用和理解一知半解,所以这篇笔记看下reflect底层代码的实现,加深对反射机制的理解
反射包中主要包含两个文件用以描述反射涉及的功能:
type.go
: 类型;空接口类型描述,eface._type
value.go
: 值;空接口值描述,eface.data
下面从这两个方向,分别介绍反射如何获取接口类型和接口值
Type
reflect.TypeOf(i interface{})
可以获取反射后得到的类型变量,我们就从TypeOf()
入手,通读type源码
// TypeOf()接收一个空接口,并返回接口类型Type
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
// emptyInterface是一个结构体,其实和空接口基础类型eface保持一致,我理解在reflect中,他就是eface
// 1. typ表示空接口类型rtype(同 eface._type)
// 2. word表示空接口值word(同 eface.data)
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// rtype是一个类型结构体,其定义必须和官方type类型(runtime._type)保持一致.
// rtype在结构和_type保持一致的基础上,实现了反射类型Type接口
type rtype struct {
size uintptr // 类型大小
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8 // 每个类型对应一个整数,和C保持一致;如Bool: 1, Int: 2
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
// 反射世界的二因素之一,类型,是一个接口,包含了所有类型的方法。但是注意,部分方法只有部分类型可以使用,否则panic;详细的限制在每一个方法定义中有说明
// 记录一些常用函数的功能
type Type interface {
// 返回当前类型大小;即当前类型的值在内存分配中需要申请多少字节的内存大小
Align() int
// 该类型值作为结构体的一个字段时,有多少字节数
FieldAlign() int
// 返回该类型方法集合中,第 i 个方法;注意,i过界时会panic
Method(int) Method
// 返回当前类型,可导出的所有方法的数量,也是Method(i int)中i的上界
NumMethod() int
// 通过字符串获取该类型对应的方法,不存在时返回false
MethodByName(string) (Method, bool)
Name() string
PkgPath() string
String() string
// 存储该类型需要多少字节;
// 这个和Align()的区别在于前者返回申请内存时的大小,Size()返回存储时的大小;具体为什么这么划分呢???
Size() uintptr
// 返回该类型的KInd;Kind是一个uint,每个类型有一个固定的值
Kind() Kind
// 该类型是否实现了接口u
Implements(u Type) bool
AssignableTo(u Type) bool
ConvertibleTo(u Type) bool
Comparable() bool
Bits() int
ChanDir() ChanDir
IsVariadic() bool
// 返回该类型元素类型???不太懂。。。
// 仅可用于Array, Chan, Map, Ptr, or Slice,否则panic
Elem() Type
// 返回结构体的第i个字段,溢出时panic
Field(i int) StructField
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
NumField() int
// 返回函数类型的第i个参数;仅函数类型可用
In(i int) Type
Out(i int) Type
NumIn() int
NumOut() int
// 返回哈希表key的类型
Key() Type
// 返回array类型的长度
Len() int
common() *rtype
uncommon() *uncommonType
}
综上,我们可以知道:
- 反射世界的
Type
是一个包含了所有类型方法的接口 TypeOf
函数隐式将输入参数转换成emptyInterface
结构体,该结构体的形式和官方空结构体的底层实现eface
一致,- 反射世界的
Type
实体是一个rtype
结构体,它的实现和官方_type保持一致,且实现了接口Type TypeOf
返回一个Type接口,其实体就是rtype
StructType
我们以StructType为例,了解下其相关接口函数的实现;其他Type则不再一一详述
type structType struct {
rtype // 继承自rtype类型
pkgPath name
fields []structField // 接口体字段列表,按照在内存中的偏移排序
}
// 结构体中每个字段的描述
type StructField struct {
Name string // 字段名
Type Type // 字段类型
Tag StructTag // 字段包含的tag
PkgPath string // 非导出字段(小写)的包路径,可导出字段(大写)为空。不明觉厉。。。
Offset uintptr // 在结构体中的偏移位置
Index []int
Anonymous bool // 是否嵌入字段
}
// 结构体中各字段对应的tag
//
// By convention, tag strings are a concatenation of
// optionally space-separated key:"value" pairs.
// Each key is a non-empty string consisting of non-control characters
// Each value is quoted using U+0022 '"' characters and Go string literal syntax.
//
// 大致翻译:tag是多个以空格连接的key:"value"串
// 其主要实现了Get()方法,用于获取指定key的值(按照上述规则解析)
type StructTag string
// 返回该结构体第i个字段
func (t *structType) Field(i int) (f StructField) {
// 溢出检测
if i < 0 || i >= len(t.fields) {
panic("reflect: Field index out of bounds")
}
// 关键一步,从字段列表中取一下就好了
// 这里注意:刚开始有个疑问,t.fields这个列表哪里来的??怎么初始化出来的??好像源码里没有对应数据结构的初始化操作
// 其实都是底层的内存操作
// 只要知道元素类型和元素存储的内存地址,底层就按照对应字段大小进行内存地址计算,就可以拿到对应的值
// 所以,所有的初始化操作其实就是数据段内存地址的赋值 => data: unsafe.Pointer(&i)
p := &t.fields[i]
f.Type = toType(p.typ)
f.Name = p.name.name()
f.Anonymous = p.embedded()
...
if tag := p.name.tag(); tag != "" {
f.Tag = StructTag(tag)
}
...
return
}
// 通过name获取字段
func (t *structType) FieldByName(name string) (f StructField, present bool) {
hasEmbeds := false
if name != "" {
// 遍历结构体的字段列表,比对name,相同则返回true
for i := range t.fields {
tf := &t.fields[i]
if tf.name.name() == name {
return t.Field(i), true
}
if tf.embedded() {
hasEmbeds = true
}
}
}
...
}
// 以外structType还实现了另外两个方法,这里不再赘述
func (t *structType) FieldByIndex(index []int) (f StructField) {}
func (t *structType) FieldByNameFunc(match func(string) bool) (result StructField, ok bool) {}
// rtype实现的接口,就是对structType的调用, 如:
func (t *rtype) FieldByName(name string) (StructField, bool) {
if t.Kind() != Struct {
panic("reflect: FieldByName of non-struct type " + t.String())
}
tt := (*structType)(unsafe.Pointer(t))
return tt.FieldByName(name)
}
Value
反射世界二因素之二Value
,我们同样以reflect.ValueOf()
函数入手,了解Value
的底层原理
// ValueOf() 返回空接口i中保存的具体值
func ValueOf(i interface{}) Value {
// 如果接口是nil,则返回空接口体Value{}
if i == nil {
return Value{}
}
// 内存逃逸;获取接口值时,统一将接口值逃逸到堆空间
escapes(i)
return unpackEface(i)
}
// 反射世界的二因素之二,值,是一个结构体实体;(不同于类型的实现,是一个接口)
type Value struct {
// 该值对应的类型,脱离类型的值是无意义的
// 元素在内存中都是一堆二进制,计算机必须知道元素的类型和内存地址,才知道从哪些内存中取哪些二进制
typ *rtype
// 数据存储的指针
ptr unsafe.Pointer
// 存储接口值的一些元数据信息
flag
}
// 好多二进制计算,还不太清楚干什么的。。。
type flag uintptr
// 从空接口中获取接口值
func unpackEface(i interface{}) Value {
// 先把i强制转换成空接口eface
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
// 空接口无类型,则返回空值
if t == nil {
return Value{}
}
// 基本就是类型对应的整形大小
f := flag(t.Kind())
// func (t *rtype) Kind() Kind { return Kind(t.kind & kindMask) }
// kindMask = (1 << 5) - 1 => 11111
if ifaceIndir(t) {
f |= flagIndir
// flagIndir flag = 1 << 7
}
return Value{t, e.word, f}
}
// ifaceIndir reports whether t is stored indirectly in an interface value.
// 看注释的意思是,判断这个类型是否简介保存在接口值里???
func ifaceIndir(t *rtype) bool {
return t.kind&kindDirectIface == 0
// kindDirectIface = 1 << 5
}
综上,我们知道关于反射世界的Value
:
Value
是一个结构体实体,该接口体包含了值的类型和数据指针ValueOf()
同样会隐式将实体转换成空接口类型emptyInteface
,并获取其类型和数据指针- 内存逃逸;
ValueOf()
会将实体对应的内存逃逸到堆上 ValueOf()
返回是一个Value
结构体
Value绑定的方法
ValueOf()
会返回一个Value
结构体,并包含值对应的类型和内存地址,基于这两个重要的数据,Value
提供了一些方法供程序员获取想要的指标,下面主要介绍几个:
// 判断接口值是不是bool类型
func (v Value) Bool() bool {
v.mustBe(Bool)
return *(*bool)(v.ptr)
}
// 返回值的容量大小;接口类型必须是array/slice/chan,否则panic
func (v Value) Cap() int {
k := v.kind()
switch k {
case Array:
return v.typ.Len()
case Chan:
return chancap(v.pointer())
case Slice:
return (*unsafeheader.Slice)(v.ptr).Cap
}
panic(&ValueError{"reflect.Value.Cap", v.kind()})
}
// 如果当前接口值仍然是一个接口类型,或者指针类型;Elem()可以获取新的Value指向其值
func (v Value) Elem() Value {
k := v.kind()
switch k {
// 接口类型,转换为eface并从eface中获取Value元素
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag.ro()
}
return x
// 指针类型则直接构造Value指向指针对应的值
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}
func (v Value) Field(i int) Value {
if v.kind() != Struct {
panic(&ValueError{"reflect.Value.Field", v.kind()})
}
// 结构体类型
tt := (*structType)(unsafe.Pointer(v.typ))
if uint(i) >= uint(len(tt.fields)) {
panic("reflect: Field index out of range")
}
// 获取第i个字段的类型
field := &tt.fields[i]
// 第i个字段的类型
typ := field.typ
...
// flag相关。。
...
// 计算第i个字段对应的内存地址
ptr := add(v.ptr, field.offset(), "same as non-reflect &v.field")
return Value{typ, ptr, fl}
}
Value
同样根据不同的类型,实现了不同的方法,以便我们便捷的获取信息;方法比较多,这里不再一一赘述