Golang源码阅读笔记 - reflect

之前在golang底层代码和业务代码中经常见到使用反射,感觉对反射的使用和理解一知半解,所以这篇笔记看下reflect底层代码的实现,加深对反射机制的理解

反射包中主要包含两个文件用以描述反射涉及的功能:

  1. type.go: 类型;空接口类型描述, eface._type
  2. 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
}

综上,我们可以知道:

  1. 反射世界的Type是一个包含了所有类型方法的接口
  2. TypeOf函数隐式将输入参数转换成emptyInterface结构体,该结构体的形式和官方空结构体的底层实现eface一致,
  3. 反射世界的Type实体是一个rtype结构体,它的实现和官方_type保持一致,且实现了接口Type
  4. 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:

  1. Value是一个结构体实体,该接口体包含了值的类型和数据指针
  2. ValueOf()同样会隐式将实体转换成空接口类型emptyInteface,并获取其类型和数据指针
  3. 内存逃逸;ValueOf()会将实体对应的内存逃逸到堆上
  4. 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同样根据不同的类型,实现了不同的方法,以便我们便捷的获取信息;方法比较多,这里不再一一赘述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值