golang 反射使用总结

BigFish_大鱼 · · 1675 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

  • 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


download.png

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比较多,但是大部分性能不好,部分接口可以优化,采用指针直接操作。平常使用中如果性能不是要求太高,反射可以极大的方便开发,如添加缓存层,在框架中添加中间件记录结果等。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:BigFish_大鱼

查看原文:golang 反射使用总结

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1675 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传