<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          golang反射的高級應(yīng)用

          共 12866字,需瀏覽 26分鐘

           ·

          2021-07-31 20:40

          大量代碼警告!!!

          1.  反射常用方法

          golang通過reflect包實現(xiàn)反射。reflect.TypeOf方法可以獲取一個對象的類型信息,reflect.ValueOf方法可以獲取一個對象的值信息,從而獲取該對象中的元素,例如結(jié)構(gòu)體struct中的成員或者切片slice的成員再或者map中的成員信息。

          通過reflect.ValueOf獲取的結(jié)構(gòu)體值信息中,如果某個成員變量是可導(dǎo)出的,則可以進行設(shè)置變量;但是如果某個成員不是可導(dǎo)出的,強制進行設(shè)置會panic,所以設(shè)置之前可以通過CanSet函數(shù)判斷是否可以設(shè)置值。

          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() {  // 定義結(jié)構(gòu)體  type struct_ struct {    // 可導(dǎo)出成員    Name int    // 不可導(dǎo)出成員    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指向的結(jié)構(gòu)體的類型 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獲取值信息并設(shè)置對象

          通過reflect.ValueOf可以獲得一個對象的值信息,從而進行對象參數(shù)的設(shè)置。

          但是,在設(shè)置新值的時候,該value需要是可以被設(shè)置的(第一個字母大寫),不然會panic,也可以在設(shè)置之前通過CanSet函數(shù)判斷。例如:

          func analysisValue() {  // 定義結(jié)構(gòu)體  type struct_ struct {    // 可導(dǎo)出成員    Name int    // 不可導(dǎo)出成員    age int  }
          // 通過結(jié)構(gòu)體獲取value值 // 不可設(shè)置值,其成員也不能設(shè)置,哪怕是可導(dǎo)出成員 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值可以進行設(shè)置值 // 只有可導(dǎo)出成員可以設(shè)置值,不可導(dǎo)出成員不能設(shè)置新值 var ptr_struct = &struct_{} var value_struct_ptr = reflect.ValueOf(ptr_struct) fmt.Println("canset of value_struct_ptr:", value_struct_ptr.CanSet()) // 可導(dǎo)出成員可以設(shè)置值 var field1_struct = value_struct_ptr.Elem().FieldByName("Name") fmt.Println("canset of field1_struct:", field1_struct.CanSet()) field1_struct.SetInt(1000) // 不可導(dǎo)出成員不能設(shè)置值 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 也可以進行值的設(shè)置 var slice = []string{"hello", "world"} // *slice是不可設(shè)置的 var value_slice_ptr = reflect.ValueOf(&slice) fmt.Println("canset of value_slice_ptr:", value_slice_ptr.CanSet()) // *slice的元素是可以設(shè)置的 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由于結(jié)構(gòu)比較復(fù)雜,關(guān)于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可以進行值的設(shè)置,但是如何通過reflect來生成對象呢。

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

          2.  設(shè)置對象參數(shù)的高級版本

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

          雖然通過reflect.TypeOf可以獲得對象的類型信息,然后通過類型信息以及reflect.ValueOf獲得的值信息來進行對象中成員或者對象值的設(shè)置。但是,golang每設(shè)置一個對象的值就需要新生成一個對應(yīng)的reflect.Value值,這樣的話,如果有多個相同reflect.Type的對象,就需要生成很多個reflect.Value值,這也是golang效率慢的原因。而在Java中,通過反射獲取的field可以作用在多個對象上。

          2.1.  通過內(nèi)存偏移的指針設(shè)置值

          其中一個方法就像是C語言中的指針偏移一樣,通過偏移指針,從而設(shè)置指針對應(yīng)的對象。這個方法還有一個優(yōu)點,就是不需要關(guān)心結(jié)構(gòu)體的成員是不是可導(dǎo)出成員,通過指針都可以設(shè)置。

          那么,golang的基本類型的內(nèi)存占用可以通過其底層數(shù)據(jù)類型得出。

          1. int 在64位機器中占用8個字節(jié),在32位機器中占用4個字節(jié)

          2. int32占用4個字節(jié)

          3. int64占用8個字節(jié)

          4. float64占用8個字節(jié)

          5. float32占用4個字節(jié)

          6.指針或者uintptr在64位機器中占用8個字節(jié),而在32位機器中占用4個字節(jié)

          7. string的底層類型是reflect.StringHeader,64位機器占用16個字節(jié), 32位機器占用8個字節(jié),定義如下。

           type StringHeader struct {     Data uintptr     Len  int }

          8. slice的底層類型是reflect.SliceHeader,64位機器占用24個字節(jié),32位機器中占用12個字節(jié),定義如下。

           type SliceHeader struct {     Data uintptr     Len  int     Cap  int }

          9. bool占用一個字節(jié)

          在計算機底層中,為了提高空間利用率,會進行內(nèi)存的字節(jié)對齊,例如bool,int32和int64三個數(shù)據(jù)放置在一起總共會占用16個字節(jié),而不是1+4+8=13個字節(jié)。其實際內(nèi)存分布如下:

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

          其中,hole是為了字節(jié)對齊而添加的空洞

          如下代碼中,A_Struct的內(nèi)存分布如下。

            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  }
          內(nèi)存分布|--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|

          通過偏移設(shè)置對象的代碼如下:

          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個字節(jié),float64占8個字節(jié),  // bool占1個字節(jié),指針ptr占8個字節(jié),string的底層是stringheader占用16個字節(jié)  // slice的底層結(jié)構(gòu)是sliceheader,map底層結(jié)構(gòu)未知,但是占用8個字節(jié)  // 在結(jié)構(gòu)體中會進行字節(jié)對齊  // 比如在bool后面跟一個ptr,bool就會對齊為8個字節(jié)  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))
          // 設(shè)置A的第一個int型成員變量 *((*int)(unsafe.Pointer(start_ptr))) = 100 fmt.Println("after set int of A: ", A_bean)
          // 設(shè)置A的第二個float32成員變量 *((*float32)(unsafe.Pointer(start_ptr + 8))) = 55.5 fmt.Println("after set float32 of A: ", A_bean)
          // 設(shè)置A的第三個bool變量 *((*bool)(unsafe.Pointer(start_ptr + 12))) = true fmt.Println("after set bool of A:", A_bean)
          // 設(shè)置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結(jié)構(gòu)體變量,所以可以繼續(xù)通過偏移來設(shè)置 // 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)
          }

          運行結(jié)果:

           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來獲取偏移量對應(yīng)的指針

          通過reflect.StructField 上有一個 Offset 的屬性,可以獲得對應(yīng)成員的指針,進而可以通過指針設(shè)置對應(yīng)的值,這樣就不用費勁的計算內(nèi)存偏移的值了。

          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個字節(jié),float64占8個字節(jié),  // bool占1個字節(jié),指針ptr占8個字節(jié),string的底層是stringheader占用16個字節(jié)  // slice的底層結(jié)構(gòu)是sliceheader,map底層結(jié)構(gòu)未知,但是占用8個字節(jié)  // 在結(jié)構(gòu)體中會進行字節(jié)對齊  // 比如在bool后面跟一個ptr,bool就會對齊為8個字節(jié)  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))
          // 設(shè)置A的第一個int型成員變量 *((*int)(unsafe.Pointer(start_ptr + type_A.Field(0).Offset))) = 100 fmt.Println("after set int of A: ", A_bean)
          // 設(shè)置A的第二個float32成員變量 *((*float32)(unsafe.Pointer(start_ptr + type_A.Field(1).Offset))) = 55.5 fmt.Println("after set float32 of A: ", A_bean)
          // 設(shè)置A的第三個bool變量 *((*bool)(unsafe.Pointer(start_ptr + type_A.Field(2).Offset))) = true fmt.Println("after set bool of A:", A_bean)
          // 設(shè)置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結(jié)構(gòu)體變量,所以可以繼續(xù)通過偏移來設(shè)置 // 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)
          }

          結(jié)果:

           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]}}

          結(jié)果表明,通過offset獲取偏移地址還是很方便的。

          3.  總結(jié)

          通過golang反射可以設(shè)置對象參數(shù),但是對于不可導(dǎo)出的對象,golang基礎(chǔ)的反射api無法進行設(shè)置;而對于可導(dǎo)出的對象,golang的反射效率又比較低下。通過操作內(nèi)存的方式進行對象的設(shè)置,雖然可能產(chǎn)生不安全性,但是極大地提高了反射的效率,也提高了反射覆蓋的范圍。



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲取;還可以回復(fù)「進群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。


          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操少妇 | 欧美精品第一页 | 走光无码一区二区三区 | 国产黄色电影免费观看 | 欧美日韩国产中文精品字幕自在 |