| 注册
请输入搜索内容

热门搜索

Java Linux MySQL PHP JavaScript Hibernate jQuery Nginx
XVMMoh
5年前发布

Go 反射实践及剖析

   <h2>Go struct拷贝</h2>    <p>在用Go做orm相关操作的时候,经常会有struct之间的拷贝。比如下面两个struct之间要拷贝共同成员B,C。这个在struct不是很大的时候从来都不是问题,直接成员拷贝即可。但是当struct的大小达到三四十个成员的时候,就要另辟蹊径了。</p>    <pre>  <code class="language-go">type A struct {      A int      B int      C string      E string  }     type B struct {      B int      C string      D int      E string  }  </code></pre>    <p>做法一. 反射</p>    <pre>  <code class="language-go">funcCopyStruct(src, dst interface{}) {      sval := reflect.ValueOf(src).Elem()      dval := reflect.ValueOf(dst).Elem()         for i := 0; i < sval.NumField(); i++ {          value := sval.Field(i)          name := sval.Type().Field(i).Name           dvalue := dval.FieldByName(name)        if dvalue.IsValid() == false {            continue        }        dvalue.Set(value)      }  }  </code></pre>    <p>做法二. json</p>    <p>先encode成json,再decode,其实golang的json包内部实现也是使用的反射,所以再大型项目中可以考虑使用ffjson来作为替代方案。</p>    <pre>  <code class="language-go">funcmain() {      a := &A{1, "a", 1}      // b := &B{"b",2,2}       aj, _ := json.Marshal(a)      b := new(B)      _ = json.Unmarshal(aj, b)       fmt.Printf("%+v", b)  }  </code></pre>    <p>关于golang中的反射机制一直是大家诟病挺多的。因为反射中使用了类型的枚举,所以效率比较低,在高性能场景中应该尽量规避,但是,对于大部分应用场景来说,牺牲一点性能来极大减少代码行数,或者说提高开发效率都是值得的。</p>    <h2>Reflect</h2>    <p>关于Reflect的接口可以参考golang的文档,也可以直接看go的源码。reflect的核心是两个,一个是Type,一个是Value。reflect的使用一般都是以下面语句开始。</p>    <h3>Value</h3>    <p>Value的定义很简单,如下。</p>    <pre>  <code class="language-go">type Value struct {      typ *rtype      ptrunsafe.Pointer //pointer-valued data or pointer to data      flag //metedata  }  </code></pre>    <p>下面针对object的类型不同来看一下reflect提供的方法。</p>    <p>built-in type</p>    <p>reflect针对基本类型提供了如下方法,以Float()为例展开。</p>    <pre>  <code class="language-go">//读  func (v Value) Float() float64 {      k := v.kind()      switch k {      case Float32:          return float64(*(*float32)(v.ptr))      case Float64:          return *(*float64)(v.ptr)      }      panic(&ValueError{"reflect.Value.Float", v.kind()})  }  func (v Value) Bool() bool  func (v Value) Bytes() []byte  func (v Value) Int() int64  func (v Value) Uint() uint64  func (v Value) String() string  func (v Value) Complex() complex128  //写  func (v Value) Set(x Value)  func (v Value) SetBool(x bool)  func (v Value) SetBytes(x []byte)  func (v Value) SetCap(n int)  func (v Value) SetComplex(x complex128)  func (v Value) SetFloat(x float64)  func (v Value) SetInt(x int64) {      v.mustBeAssignable()      switch k := v.kind(); k {      default:          panic(&ValueError{"reflect.Value.SetInt", v.kind()})      case Int:          *(*int)(v.ptr) = int(x)      case Int8:          *(*int8)(v.ptr) = int8(x)      case Int16:          *(*int16)(v.ptr) = int16(x)      case Int32:        *(*int32)(v.ptr) = int32(x)      case Int64:        *(*int64)(v.ptr) = x      }  }  func (v Value) SetLen(n int)  func (v Value) SetMapIndex(key, valValue)  func (v Value) SetPointer(x unsafe.Pointer)  func (v Value) SetString(x string)  func (v Value) SetUint(x uint64)  </code></pre>    <p>可以看到内部Float内部实现先通过kind()判断value的类型,然后通过指针取数据并做类型转换。kind()的定义如下</p>    <pre>  <code class="language-go">func (f flag) kind() Kind {   return Kind(f & flagKindMask)  }  </code></pre>    <p>flag是Value的内部成员,所以方法也被继承过来。通过实现不难猜到reflect对不同的类型是通过一个整数来实现的。我们来验证一下,在type.go文件中找到Kind定义,注意这个地方Kind()只是一个类型转换。</p>    <pre>  <code class="language-go">typeKinduint     const (      InvalidKind = iota      Bool      Int      Int8      Int16      Int32      ...  )  </code></pre>    <p>紧挨着Kind定义的下面就是各种类型的表示:Bool=1, Int=2,…再来看一下 f &amp; flagKindMask 的意思。再 value.go 文件中找到 flagKindMask 的定义:</p>    <pre>  <code class="language-go">const (      flagKindWidth = 5 // there are 27 kinds      flagKindMaskflag = 1<<flagKindWidth - 1      ...  }  </code></pre>    <p>所以这句 f &amp; flagKindMask 的意思就是最f的低5位,也就是对应了上面的各种类型。</p>    <p>上面说完了数据的读接口,其实写接口也很类似。唯一的区别在于reflect还提供了一个Set()方法,就是说我们自己去保证数据的类型正确性。</p>    <p>Struct</p>    <p>其实使用反射的很多场景都是struct。reflect针对struct提供的函数方法如下:</p>    <pre>  <code class="language-go">func (v Value) Elem() Value //返回指针或者interface包含的值  func (v Value) Field(i int) Value //返回struct的第i个field  func (v Value) FieldByIndex(index []int) Value //返回嵌套struct的成员  func (v Value) FieldByName(namestring) Value //通过成员名称返回对应的成员  func (v Value) FieldByNameFunc(matchfunc(string) bool) Value //只返回满足函数match的第一个field  </code></pre>    <p>通过上面的方法不出意外就可以取得对应是struct field了。</p>    <p>其他类型:Array, Slice, String</p>    <p>对于其他类型,reflect也提供了获得其内部成员的方法。</p>    <pre>  <code class="language-go">func (v Value) Len() int //Array, Chan, Map, Slice, or String  func (v Value) Index(i int) Value //Array, Slice, String  func (v Value) Cap() int //Array, Chan, Slice  func (v Value) Close() //Chan  func (v Value) MapIndex(keyValue) Value //Map  func (v Value) MapKeys() []Value //Map  </code></pre>    <p>函数调用</p>    <p>reflect当然也可以实现函数调用,下面是一个简单的例子。</p>    <pre>  <code class="language-go">funcmain() {      var f = func() {          fmt.Println("hello world")      }         fun := reflect.ValueOf(f)      fun.Call(nil)  }  //Output  helloworld  </code></pre>    <p>当然我们还可以通过struct来调用其方法,需要注意的一点是通过反射调用的函数必须是外部可见的(首字母大写)。</p>    <pre>  <code class="language-go">type S struct {      A int  }     func (s S) Method1() {      fmt.Println("method 1")  }     func (s S) Method2() {      fmt.Println("method 2")  }     funcmain() {      var a S      S := reflect.ValueOf(a)         fmt.Println(S.NumMethod())         m1 :=S.Method(0)      m1.Call(nil)         m2 := S.MethodByName("Method2")      m2.Call(nil)  }  </code></pre>    <p>总结一下reflect提供了函数调用的相关接口如下:</p>    <pre>  <code class="language-go">func (v Value) Call(in []Value) []Value  func (v Value) CallSlice(in []Value) []Value  func (v Value) Method(i int) Value //v's ith function  func (v Value) NumMethod() int  func (v Value) MethodByName(namestring) Value  </code></pre>    <h3>Type</h3>    <p>Type是反射中另外重要的一部分。Type是一个接口,里面包含很多方法,通常我们可以通过 reflect.TypeOf(obj) 取得obj的类型对应的Type接口。接口中很多方法都是对应特定类型的,所以调用的时候需要注意。</p>    <pre>  <code class="language-go">type Type interface {      Align() int      FieldAlign() int      Method(int) Method      MethodByName(string) (Method, bool)      NumMethod() int      ...  </code></pre>    <p>另外reflect包还为我们提供了几个生成特定类型的Type接口的方法。这里就不一一列举了。</p>    <h2>小结</h2>    <p>关于reflect提供的接口需要注意的一点就是,一定要保证类型是匹配的,如果不匹配将导致panic。关于Value的主要接口都在这,本来还想写一下Type以及内部的实现机制的,只能放到下篇再写了。</p>    <p> </p>    <p>来自:http://blog.jobbole.com/108601/</p>    <p> </p>    
 本文由用户 XVMMoh 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
 转载本站原创文章,请注明出处,并保留原始链接、图片水印。
 本站是一个以用户分享为主的开源技术平台,欢迎各类分享!
 本文地址:https://www.open-open.com/lib/view/open1481264231374.html
Go语言 Google Go/Golang开发