golang 反射的高級應用

大量代碼警告!!!

  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: int
slices's kind: slice , name:
ele_slices's kind: string , name: string
type_mmap's kind: map , name:
ele_key_mmap's kind: int , name: int
ele_value_mmap's kind: string , name: string
type_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: false
canset of ele_struct: false
canset of value_struct_ptr: false
canset of field1_struct: true
canset of field2_struct: false
the struct after set: &{1000 0}
canset of value_slice_ptr: false
canset of ele2_slice: true
slice after set: [hello balabala]
slice after append: [hello balabala world]
canset of value_map_ptr: false
canset of value_of_map: false

1.3.  通過 reflect 包獲取初值

之前,通過 reflect.ValueOf 可以進行值的設置,但是如何通過 reflect 來生成對象呢。

通過下圖中的 api 可以通過 reflect.Type 新建 reflect.Value 值

  1.  設置對象參數的高級版本 ===============

參考: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
 }
  1. slice 的底層類型是 reflect.SliceHeader,64 位機器佔用 24 個字節,32 位機器中佔用 12 個字節,定義如下。
 type SliceHeader struct {
     Data uintptr
     Len  int
     Cap  int
 }
  1. 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 獲取偏移地址還是很方便的。

  1.  總結 ======

通過 golang 反射可以設置對象參數,但是對於不可導出的對象,golang 基礎的反射 api 無法進行設置;而對於可導出的對象,golang 的反射效率又比較低下。通過操作內存的方式進行對象的設置,雖然可能產生不安全性,但是極大地提高了反射的效率,也提高了反射覆蓋的範圍。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/U9ZPr70sFC3KuZtyNmDBBQ