- 1、反射三法则
- 2、反射用法
- 3、反射性能
- 4、反射总结
反射是一种检测结构特别是类型的能力,属于元编程的一种(用程序来生成代码)。go是静态类型。每个变量都有个静态类型,类型在编译阶段就确定了。
为了更好的理解反射,interface需要首先了解。它代表方法的集合。interface的变量可以保存任何的变量,只要变量实现了接口的方法。当interface的方法个数为0个时可以代表所有的变量。
var r io.Readerr = os.Stdinr = bufio.NewReader(r)
r = new(bytes.Buffer)// and so on
所有的反射都和接口关联。
接口变量保存存有两个值:具体的值和它的类型描述,但是接口只判断什么方法可以被调用,变量可能有更多的方法可以调用。
1、反射三条法则
先看个例子
//这是接口,只是定义了GetName方法
type MyInterface interface {
GetName() string
}
type Student struct {
}
func (s *Student)GetName() string {
return "test"
}
func (s *Student)GetAge() string {
return "10"
}
func main() {
var myi MyInterface
stu := Student{}
myi = &stu
//myi只能调用GetName,但是他还有GetAge方法
myi.GetName()
接口类型变为Student类型可以这样
i := myi.(*Student)
fmt.Print(i.GetAge())
有了这个基础,然后我们看下golang反射的三个法则
- 1、反射可以把接口值转换为反射类型对象
注:反射类型指reflect.Value和reflect.Type
type和value
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
x并不是接口类型,当调用typeof的时候会将x存储在一个空的interface{}变量中,然后作为参数传过去。
- 2、反射可以把反射类型对象转换为接口值
例如下面的代码,通过value的interface方法,重新把value包装成interface{},然后转成float64
var x float64 = 3.4
value := reflect.ValueOf(x)
v := value.Interface().(float64)
fmt.Println(v)
- 3、利用反射修改变量值,这个值必须是可写的
是否可以设置类型是否可以找到地址
golang中方法都是值传递,直接传递值,则值被赋值了,无法找到原来的地址,更不可能赋值。因此通过反射修改值的时候,变量必须是指针类型。
2、反射用法
- map and slice
func MapAndSlice() {
stringSlice := make([]string,0)
stringMap := make(map[string]string)
sliceType := reflect.TypeOf(stringSlice)
mapType := reflect.TypeOf(stringMap)
rMap := reflect.MakeMap(mapType)
rSlice := reflect.MakeSlice(sliceType,0,0)
k := "first"
rMap.SetMapIndex(reflect.ValueOf(k),reflect.ValueOf("test"))
i := rMap.Interface().(map[string]string)
fmt.Println(i)
reflect.AppendSlice(rSlice,reflect.ValueOf("test slice"))
strings := rSlice.Interface().([]string)
fmt.Println(strings)
}
- function
func MakeFun()interface{} {
f := timeMe
vf := reflect.ValueOf(f)
return reflect.MakeFunc(reflect.TypeOf(f),func(in []reflect.Value) []reflect.Value {
start := time.Now()
out := vf.Call(in)
end := time.Now()
fmt.Printf("calling %s took %v\n", runtime.FuncForPC(vf.Pointer()).Name(), end.Sub(start))
return out
}).Interface()
}
- struct
func MakeStruct(vals ...interface{}) interface{} {
var sfs []reflect.StructField
for k, v := range vals {
t := reflect.TypeOf(v)
sf := reflect.StructField{
Name: fmt.Sprintf("F%d", (k + 1)),
Type: t,
}
sfs = append(sfs, sf)
}
st := reflect.StructOf(sfs)
so := reflect.New(st)
return so.Interface()
}
- 获取type typeOf valueOf
//type和value
m := MyStruct{"test",10}
t := reflect.TypeOf(m)
fmt.Println(t)
v := reflect.ValueOf(&m)
fmt.Println(v)
//读取
for i := 0; i < t.NumField() ;i++ {
fmt.Printf("name:%s,json_tag:%s",t.Field(i).Name,t.Field(i).Tag.Get("json"))
fmt.Println()
}
//设置
v.Elem().Field(0).SetString("test1")
v.Elem().Field(1).SetInt(31)
//读取
for i := 0; i < t.NumField() ;i++ {
fmt.Printf("name:%v",v.Elem().Field(i))
fmt.Println()
}
- 反射调用struct方法
//带参数调用方式
setNameMethod := v.MethodByName( "AddAge" )
args := []reflect.Value{ reflect.ValueOf(10) } //构造一个类型为reflect.Value的切片
setNameMethod.Call(args) //返回Value类型
fmt.Println("User.Age = ",m.Age)
这些都是例子,下面是个脑图,包含了常用的api
3、反射性能
都说反射性能不好,那具体是多少呢,这里做了个性能测试
3.1、map和slice测试
func Benchmark_MapSet(b *testing.B) {
var s = make(map[int]int)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s[10] = 10
}
_ = s
}
func Benchmark_MapReflectSet(b *testing.B) {
m := make(map[int]int)
mv := reflect.ValueOf(m)
k := reflect.ValueOf(10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
mv.SetMapIndex(k,k)
}
_ = m
}
func Benchmark_sliceAppend(b *testing.B) {
var s = make([]int,0)
v := 10;
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = append(s, v)
}
_ = s
}
func Benchmark_sliceReflectAppend(b *testing.B) {
var tmp []int
ty := reflect.TypeOf(tmp)
var s = reflect.MakeSlice(ty,0,0)
v := reflect.ValueOf(10);
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = reflect.Append(s,v)
}
_ = s
}
func Benchmark_SliceNew(b *testing.B) {
var s []int
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = make([]int,0)
}
_ = s
}
func Benchmark_SliceNewReflect(b *testing.B) {
var s reflect.Value
var tmp []int
ty := reflect.TypeOf(tmp)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = reflect.MakeSlice(ty,0,0)
}
_ = s
}
func Benchmark_MapNew(b *testing.B) {
var s map[int]int
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = make(map[int]int)
}
_ = s
}
func Benchmark_MapReflectNew(b *testing.B) {
var s reflect.Value
var tmp map[int]int
ty := reflect.TypeOf(tmp)
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = reflect.MakeMap(ty)
}
_ = s
}
运行结果
直接mapset Benchmark_MapSet-8 100000000 13.5 ns/op
反射调用mapset Benchmark_MapReflectSet-8 30000000 54.3 ns/op
slice直接append Benchmark_sliceAppend-8 100000000 22.4 ns/op
反射方式append Benchmark_sliceReflectAppend-8 20000000 101 ns/op
创建slice Benchmark_SliceNew-8 300000000 5.10 ns/op
反射创建slice Benchmark_SliceNewReflect-8 30000000 57.9 ns/op
创建map Benchmark_MapNew-8 30000000 45.9 ns/op
反射创建map Benchmark_MapReflectNew-8 20000000 89.0 ns/op
可以看到发射大部分比直接操作慢10倍以上,map的创建慢两倍左右
3.2、struct 操作
type MyInter interface {
GetName() string
}
type Student struct {
Name string
Age int
Class string
Score int
}
func (s *Student)GetName() string {
return s.Name
}
func BenchmarkReflect_New(b *testing.B) {
var s *Student
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv)
s, _ = sn.Interface().(*Student)
}
_ = s
}
func BenchmarkDirect_New(b *testing.B) {
var s *Student
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = new(Student)
}
_ = s
}
func BenchmarkReflect_Set(b *testing.B) {
var s *Student
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv)
s = sn.Interface().(*Student)
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
}
}
func BenchmarkReflect_SetFieldByName(b *testing.B) {
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv).Elem()
sn.FieldByName("Name").SetString("Jerry")
sn.FieldByName("Age").SetInt(18)
sn.FieldByName("Class").SetString("20005")
sn.FieldByName("Score").SetInt(100)
}
}
func BenchmarkReflect_SetFieldByIndex(b *testing.B) {
sv := reflect.TypeOf(Student{})
b.ResetTimer()
for i := 0; i < b.N; i++ {
sn := reflect.New(sv).Elem()
sn.Field(0).SetString("Jerry")
sn.Field(1).SetInt(18)
sn.Field(2).SetString("20005")
sn.Field(3).SetInt(100)
}
}
func BenchmarkDirect_Set(b *testing.B) {
var s *Student
b.ResetTimer()
for i := 0; i < b.N; i++ {
s = new(Student)
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
}
}
func DirectInvoke(s *Student) {
s.Name = "Jerry"
s.Age = 18
s.Class = "20005"
s.Score = 100
}
func BenchmarkDirectInvoke(b *testing.B) {
s := new(Student)
for i := 0; i < b.N; i++ {
DirectInvoke(s)
}
_ = s
}
直接创建strcuct BenchmarkDirect_New-8 30000000 41.0 ns/op
反射创建strcut BenchmarkReflect_New-8 20000000 61.9 ns/op
直接设置field BenchmarkReflect_Set-8 20000000 65.2 ns/op
根据名字反射设置 BenchmarkReflect_SetFieldByName-8 3000000 416 ns/op
根据index反射设置 BenchmarkReflect_SetFieldByIndex-8 20000000 108 ns/op
性能也都慢10倍以上
3.3、反射方法调用
func Benchmark_FunCall(b *testing.B) {
var s = &Student{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ =s.GetName()
}
}
func BenchmarkMy_FunReflectCall(b *testing.B) {
var s = &Student{}
v :=reflect.ValueOf(s)
f := v.MethodByName("GetName")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = f.Call(nil)
}
}
直接调用 Benchmark_FunCall-8 2000000000 0.31 ns/op
反射调用 BenchmarkMy_FunReflectCall-8 5000000 283 ns/op
反射的call满了200倍以上
3.4 性能优化
通过上面的测试可以发现反射确实性能不好,大部分api都慢10倍以上,尤其是方法调用。哪有没有优化方法呢?有的,有部分api可以。这个可以参考 https://github.com/json-iterator/go, 内存和速度都比标准库优秀,下面是个原理的示例,根据field偏移量设置field值
func BenchmarkMyFun(b *testing.B) {
struct_ := &Student{}
sn := reflect.TypeOf(struct_)
b.ResetTimer()
for i := 0; i < b.N; i++ {
field0 := sn.Elem().Field(0)
field0Ptr := uintptr(unsafe.Pointer(struct_)) + field0.Offset
*((*string)(unsafe.Pointer(field0Ptr))) = "Jerry"
field1 := sn.Elem().Field(1)
field1Ptr := uintptr(unsafe.Pointer(struct_)) + field1.Offset
*((*int)(unsafe.Pointer(field1Ptr))) = 18
field2 := sn.Elem().Field(2)
field2Ptr := uintptr(unsafe.Pointer(struct_)) + field2.Offset
*((*string)(unsafe.Pointer(field2Ptr))) = "20005"
field3 := sn.Elem().Field(3)
field3Ptr := uintptr(unsafe.Pointer(struct_)) + field3.Offset
*((*int)(unsafe.Pointer(field3Ptr))) = 100
}
}
enchmarkReflect_Set-8 20000000 65.2 ns/op
BenchmarkReflect_SetFieldByName-8 3000000 416 ns/op
BenchmarkMyFun-8 10000000 178 ns/op
这个比反射快了4倍左右。其他原理可以自行搜索,网上很多,初次之外还可以看下reflect2,对反射的指针操作做了一个封装,在性能要求比较高的地方可以考虑使用。
4、反射总结
反射有三个原则,他可以转为接口,也可以从接口中获取反射的数据。反射的api比较多,但是大部分性能不好,部分接口可以优化,采用指针直接操作。平常使用中如果性能不是要求太高,反射可以极大的方便开发,如添加缓存层,在框架中添加中间件记录结果等。
有疑问加站长微信联系(非本文作者)