golang反射的高级应用

Go语言精选

共 12866字,需浏览 26分钟

 ·

2021-07-31 20:40

大量代码警告!!!

1.  反射常用方法

golang通过reflect包实现反射。reflect.TypeOf方法可以获取一个对象的类型信息,reflect.ValueOf方法可以获取一个对象的值信息,从而获取该对象中的元素,例如结构体struct中的成员或者切片slice的成员再或者map中的成员信息。

通过reflect.ValueOf获取的结构体值信息中,如果某个成员变量是可导出的,则可以进行设置变量;但是如果某个成员不是可导出的,强制进行设置会panic,所以设置之前可以通过CanSet函数判断是否可以设置值。

1.1.  通过reflect.TypeOf获取类型信息

通过reflect.TypeOf获得类型Type,然后通过Type.Kind()获得具体类型信息,通过Type.Name()获得类型名称。

如果是slice,可以通过Type.Elem()获得slice中元素的类型。如果是*...可以通过Type.Elem()获得指针指向的元素的类型。如果是map,可以通过Type.Key()获得map中key的类型,通过Type.Elem()获得map中value的类型

func analysisType() {  // 定义结构体  type struct_ struct {    // 可导出成员    Name int    // 不可导出成员    age int  }  // struct_的类型  var type_struct = reflect.TypeOf(struct_{})  fmt.Println("struct's kind:", type_struct.Kind(), ",name:", type_struct.Name())
// 获取struct_的第一个成员类型 var field_struct_Name, _ = type_struct.FieldByName("Name") fmt.Println("field_struct_name's kind:", field_struct_Name.Type.Kind(), ", name:", field_struct_Name.Type.Name())
// slice var slices = []string{"hello", "world"} // 获取slice的类型 var type_slice = reflect.TypeOf(slices) fmt.Println("slices's kind:", type_slice.Kind(), ", name:", type_slice.Name()) // 获取slice中元素的类型 var ele_slice = type_slice.Elem() fmt.Println("ele_slices's kind:", ele_slice.Kind(), ", name:", ele_slice.Name())
// map var mmap = make(map[int]string) // 获取map的类型 var type_mmap = reflect.TypeOf(mmap) fmt.Println("type_mmap's kind:", type_mmap.Kind(), ", name:", type_mmap.Name()) // 获取map的key的类型 var ele_key_mmap = type_mmap.Key() fmt.Println("ele_key_mmap's kind:", ele_key_mmap.Kind(), ", name:", ele_key_mmap.Name()) // 获取map的value的类型 var ele_value_mmap = type_mmap.Elem() fmt.Println("ele_value_mmap's kind:", ele_value_mmap.Kind(), ", name:", ele_value_mmap.Name())
// *struct的类型 var type_ptr = reflect.TypeOf(&struct_{}) fmt.Println("type_ptr's kind:", type_ptr.Kind(), ", name:", type_ptr.Name()) // *struct指向的结构体的类型 var content_ptr = type_ptr.Elem() fmt.Println("content_ptr's kind:", content_ptr.Kind(), ", name:", content_ptr.Name())}

输出:

struct's kind: struct ,name: struct_field_struct_name's kind: int , name: intslices's kind: slice , name:ele_slices's kind: string , name: stringtype_mmap's kind: map , name:ele_key_mmap's kind: int , name: intele_value_mmap's kind: string , name: stringtype_ptr's kind: ptr , name:content_ptr's kind: struct , name: struct_

1.2.  通过reflect.ValueOf获取值信息并设置对象

通过reflect.ValueOf可以获得一个对象的值信息,从而进行对象参数的设置。

但是,在设置新值的时候,该value需要是可以被设置的(第一个字母大写),不然会panic,也可以在设置之前通过CanSet函数判断。例如:

func analysisValue() {  // 定义结构体  type struct_ struct {    // 可导出成员    Name int    // 不可导出成员    age int  }
// 通过结构体获取value值 // 不可设置值,其成员也不能设置,哪怕是可导出成员 var value_struct = reflect.ValueOf(struct_{}) fmt.Println("canset of value_struct:", value_struct.CanSet()) var ele_struct = value_struct.FieldByName("Name") fmt.Println("canset of ele_struct:", ele_struct.CanSet())
// 通过*struct获取的value值可以进行设置值 // 只有可导出成员可以设置值,不可导出成员不能设置新值 var ptr_struct = &struct_{} var value_struct_ptr = reflect.ValueOf(ptr_struct) fmt.Println("canset of value_struct_ptr:", value_struct_ptr.CanSet()) // 可导出成员可以设置值 var field1_struct = value_struct_ptr.Elem().FieldByName("Name") fmt.Println("canset of field1_struct:", field1_struct.CanSet()) field1_struct.SetInt(1000) // 不可导出成员不能设置值 var field2_struct = value_struct_ptr.Elem().FieldByName("age") fmt.Println("canset of field2_struct:", field2_struct.CanSet()) fmt.Println("the struct after set:", ptr_struct)
// *slice 也可以进行值的设置 var slice = []string{"hello", "world"} // *slice是不可设置的 var value_slice_ptr = reflect.ValueOf(&slice) fmt.Println("canset of value_slice_ptr:", value_slice_ptr.CanSet()) // *slice的元素是可以设置的 var ele2_slice = value_slice_ptr.Elem().Index(1) fmt.Println("canset of ele2_slice:", ele2_slice.CanSet()) ele2_slice.SetString("balabala") fmt.Println("slice after set:", slice) var slice_after_append = reflect.Append(value_slice_ptr.Elem(), reflect.ValueOf("world")) fmt.Println("slice after append:", slice_after_append)
// map由于结构比较复杂,关于map中元素的修改不太可能 var mmap = make(map[int]string) mmap[1] = "hello" mmap[2] = "world" var value_map_ptr = reflect.ValueOf(&mmap) fmt.Println("canset of value_map_ptr:", value_map_ptr.CanSet())
var value_of_map = value_map_ptr.Elem().MapIndex(reflect.ValueOf(1)) fmt.Println("canset of value_of_map:", value_of_map.CanSet())}

输出:

canset of value_struct: falsecanset of ele_struct: falsecanset of value_struct_ptr: falsecanset of field1_struct: truecanset of field2_struct: falsethe struct after set: &{1000 0}canset of value_slice_ptr: falsecanset of ele2_slice: trueslice after set: [hello balabala]slice after append: [hello balabala world]canset of value_map_ptr: falsecanset of value_of_map: false

1.3.  通过reflect包获取初值

之前,通过reflect.ValueOf可以进行值的设置,但是如何通过reflect来生成对象呢。

通过下图中的api可以通过reflect.Type新建reflect.Value值

2.  设置对象参数的高级版本

参考:https://zhuanlan.zhihu.com/p/25474088

虽然通过reflect.TypeOf可以获得对象的类型信息,然后通过类型信息以及reflect.ValueOf获得的值信息来进行对象中成员或者对象值的设置。但是,golang每设置一个对象的值就需要新生成一个对应的reflect.Value值,这样的话,如果有多个相同reflect.Type的对象,就需要生成很多个reflect.Value值,这也是golang效率慢的原因。而在Java中,通过反射获取的field可以作用在多个对象上。

2.1.  通过内存偏移的指针设置值

其中一个方法就像是C语言中的指针偏移一样,通过偏移指针,从而设置指针对应的对象。这个方法还有一个优点,就是不需要关心结构体的成员是不是可导出成员,通过指针都可以设置。

那么,golang的基本类型的内存占用可以通过其底层数据类型得出。

1. int 在64位机器中占用8个字节,在32位机器中占用4个字节

2. int32占用4个字节

3. int64占用8个字节

4. float64占用8个字节

5. float32占用4个字节

6.指针或者uintptr在64位机器中占用8个字节,而在32位机器中占用4个字节

7. string的底层类型是reflect.StringHeader,64位机器占用16个字节, 32位机器占用8个字节,定义如下。

 type StringHeader struct {     Data uintptr     Len  int }

8. slice的底层类型是reflect.SliceHeader,64位机器占用24个字节,32位机器中占用12个字节,定义如下。

 type SliceHeader struct {     Data uintptr     Len  int     Cap  int }

9. bool占用一个字节

在计算机底层中,为了提高空间利用率,会进行内存的字节对齐,例如bool,int32和int64三个数据放置在一起总共会占用16个字节,而不是1+4+8=13个字节。其实际内存分布如下:

 |--bool-- 1 byte| |--hole-- 3byte| |--int32-- 4byte| |--int-- 8 byte|

其中,hole是为了字节对齐而添加的空洞

如下代码中,A_Struct的内存分布如下。

  type B_struct struct {    B_int    int    B_string string    B_slice  []string    B_map    map[int]string  }  type A_struct struct {    a_int   int    A_float float32    A_bool  bool    A_BPtr  *B_struct    A_Bean  B_struct  }
内存分布|--a_int-- 8 byte||--A_float-- 4 byte| |--A_bool-- 1 byte| |-- hole -- 3byte||--A_BPtr-- 8 byte||--B_int of A_Bbean-- 8byte||-- B_string of A_Bbean-- 16 byte||-- B_slice of A_Bbean-- 24 byte||-- B_map of A_Bbean-- 8 byte|

通过偏移设置对象的代码如下:

func testInject() {  type B_struct struct {    B_int    int    B_string string    B_slice  []string    B_map    map[int]string  }  type A_struct struct {    a_int   int    A_float float32    A_bool  bool    A_BPtr  *B_struct    A_Bean  B_struct  }  // 在64位机器中,int占8个字节,float64占8个字节,  // bool占1个字节,指针ptr占8个字节,string的底层是stringheader占用16个字节  // slice的底层结构是sliceheader,map底层结构未知,但是占用8个字节  // 在结构体中会进行字节对齐  // 比如在bool后面跟一个ptr,bool就会对齐为8个字节  fmt.Println("total size of A:", reflect.TypeOf(A_struct{}).Size())  fmt.Println("total size of B:", reflect.TypeOf(B_struct{}).Size())
var A_bean = A_struct{} var start_ptr = uintptr(unsafe.Pointer(&A_bean))
// 设置A的第一个int型成员变量 *((*int)(unsafe.Pointer(start_ptr))) = 100 fmt.Println("after set int of A: ", A_bean)
// 设置A的第二个float32成员变量 *((*float32)(unsafe.Pointer(start_ptr + 8))) = 55.5 fmt.Println("after set float32 of A: ", A_bean)
// 设置A的第三个bool变量 *((*bool)(unsafe.Pointer(start_ptr + 12))) = true fmt.Println("after set bool of A:", A_bean)
// 设置A的第四个ptr变量 var first_B = &B_struct{ B_int: 1024, B_string: "hello", B_slice: []string{"lalla", "biubiu"}, B_map: map[int]string{ 1: "this is a one", 2: "this is a two", }, } *((**B_struct)(unsafe.Pointer(start_ptr + 16))) = first_B fmt.Println("after set A_BPtr of A:", A_bean, "and A_bean.A_BPtr:", A_bean.A_BPtr)
// A的第五个变量是一个B_struct结构体变量,所以可以继续通过偏移来设置 // A的第五个变量中的第一个int变量 *((*int)(unsafe.Pointer(start_ptr + 24))) = 2048 fmt.Println("after set B_int of A_Bbean of A:", A_bean) // A的第五个变量中的第二个string变量 *((*string)(unsafe.Pointer(start_ptr + 32))) = "world" fmt.Println("after set B_string of A_Bbean of A:", A_bean) // A的第五个变量中的第三个slice变量 *((*[]string)(unsafe.Pointer(start_ptr + 48))) = []string{"hehe", "heihei"} fmt.Println("after set B_slice of A_Bbean of A:", A_bean) // A的第六个变量中的第三个slice变量 *((*map[int]string)(unsafe.Pointer(start_ptr + 72))) = map[int]string{ 3: "this is three", 4: "this is four", } fmt.Println("after set B_map of A_Bbean of A:", A_bean)
}

运行结果:

 total size of A: 80 total size of B: 56 after set int of A:  {100 0 false <nil> {0  [] map[]}} after set float32 of A:  {100 55.5 false <nil> {0  [] map[]}} after set bool of A: {100 55.5 true <nil> {0  [] map[]}} after set A_BPtr of A: {100 55.5 true 0xc0000d6040 {0  [] map[]}} and A_bean.A_BPtr: &{1024 hello [lalla biubiu] map[1:this is a one 2:this is a two]} after set B_int of A_Bbean of A: {100 55.5 true 0xc0000d6040 {2048  [] map[]}} after set B_string of A_Bbean of A: {100 55.5 true 0xc0000d6040 {2048 world [] map[]}} after set B_slice of A_Bbean of A: {100 55.5 true 0xc0000d6040 {2048 world [hehe heihei] map[]}} after set B_map of A_Bbean of A: {100 55.5 true 0xc0000d6040 {2048 world [hehe heihei] map[3:this is three 4:this is four]}}

2.2.  通过StructField.offset来获取偏移量对应的指针

通过reflect.StructField 上有一个 Offset 的属性,可以获得对应成员的指针,进而可以通过指针设置对应的值,这样就不用费劲的计算内存偏移的值了。

func testInjectWithOffset() {  type B_struct struct {    B_int    int    B_string string    B_slice  []string    B_map    map[int]string  }  type A_struct struct {    A_int   int    A_float float32    A_bool  bool    A_BPtr  *B_struct    A_Bean  B_struct  }  // 在64位机器中,int占8个字节,float64占8个字节,  // bool占1个字节,指针ptr占8个字节,string的底层是stringheader占用16个字节  // slice的底层结构是sliceheader,map底层结构未知,但是占用8个字节  // 在结构体中会进行字节对齐  // 比如在bool后面跟一个ptr,bool就会对齐为8个字节  fmt.Println("total size of A:", reflect.TypeOf(A_struct{}).Size())  fmt.Println("total size of B:", reflect.TypeOf(B_struct{}).Size())
var type_A = reflect.TypeOf(A_struct{}) var type_B = reflect.TypeOf(B_struct{})
var A_bean = A_struct{} var start_ptr = uintptr(unsafe.Pointer(&A_bean))
// 设置A的第一个int型成员变量 *((*int)(unsafe.Pointer(start_ptr + type_A.Field(0).Offset))) = 100 fmt.Println("after set int of A: ", A_bean)
// 设置A的第二个float32成员变量 *((*float32)(unsafe.Pointer(start_ptr + type_A.Field(1).Offset))) = 55.5 fmt.Println("after set float32 of A: ", A_bean)
// 设置A的第三个bool变量 *((*bool)(unsafe.Pointer(start_ptr + type_A.Field(2).Offset))) = true fmt.Println("after set bool of A:", A_bean)
// 设置A的第四个ptr变量 var first_B = &B_struct{ B_int: 1024, B_string: "hello", B_slice: []string{"lalla", "biubiu"}, B_map: map[int]string{ 1: "this is a one", 2: "this is a two", }, } *((**B_struct)(unsafe.Pointer(start_ptr + type_A.Field(3).Offset))) = first_B fmt.Println("after set A_BPtr of A:", A_bean, "and A_bean.A_BPtr:", A_bean.A_BPtr)
// A的第五个变量是一个B_struct结构体变量,所以可以继续通过偏移来设置 // A的第五个变量中的第一个int变量 *((*int)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(0).Offset))) = 2048 fmt.Println("after set B_int of A_Bbean of A:", A_bean) // A的第五个变量中的第二个string变量 *((*string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(1).Offset))) = "world" fmt.Println("after set B_string of A_Bbean of A:", A_bean) // A的第五个变量中的第三个slice变量 *((*[]string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(2).Offset))) = []string{"hehe", "heihei"} fmt.Println("after set B_slice of A_Bbean of A:", A_bean) // A的第六个变量中的第三个slice变量 *((*map[int]string)(unsafe.Pointer(start_ptr + type_A.Field(4).Offset + type_B.Field(3).Offset))) = map[int]string{ 3: "this is three", 4: "this is four", } fmt.Println("after set B_map of A_Bbean of A:", A_bean)
}

结果:

 total size of A: 80 total size of B: 56 after set int of A:  {100 0 false <nil> {0  [] map[]}} after set float32 of A:  {100 55.5 false <nil> {0  [] map[]}} after set bool of A: {100 55.5 true <nil> {0  [] map[]}} after set A_BPtr of A: {100 55.5 true 0xc000018100 {0  [] map[]}} and A_bean.A_BPtr: &{1024 hello [lalla biubiu] map[1:this is a one 2:this is a two]} after set B_int of A_Bbean of A: {100 55.5 true 0xc000018100 {2048  [] map[]}} after set B_string of A_Bbean of A: {100 55.5 true 0xc000018100 {2048 world [] map[]}} after set B_slice of A_Bbean of A: {100 55.5 true 0xc000018100 {2048 world [hehe heihei] map[]}} after set B_map of A_Bbean of A: {100 55.5 true 0xc000018100 {2048 world [hehe heihei] map[3:this is three 4:this is four]}}

结果表明,通过offset获取偏移地址还是很方便的。

3.  总结

通过golang反射可以设置对象参数,但是对于不可导出的对象,golang基础的反射api无法进行设置;而对于可导出的对象,golang的反射效率又比较低下。通过操作内存的方式进行对象的设置,虽然可能产生不安全性,但是极大地提高了反射的效率,也提高了反射覆盖的范围。



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报