童卓何烨 發表於 2019-9-20 16:49:00

深入理解Go语言(01): interface源码分析

<p>分析接口的赋值,反射,断言的实现原理<br></p>
<blockquote>
<p>版本:golang v1.12</p>
</blockquote>
<p><br>interface底层使用2个struct表示的:<code>eface</code>和<code>iface</code><br></p>
<p></p>
<h2 id="一接口类型分为2个">一:接口类型分为2个</h2>
<p></p>
<h3 id="1-空接口">1. 空接口</h3>
<pre><code>//比如
var i interface{}
</code></pre>
<p></p>
<h3 id="2-带方法的接口">2. 带方法的接口</h3>
<pre><code>//比如
type studenter interface {
    GetName() string
    GetAge()int
}
</code></pre>
<p></p>
<h2 id="二eface空接口定义">二:eface&nbsp;空接口定义</h2>
<p><br>空接口通过<code>eface</code>结构体定义实现,位于<code>src/runtime/runtime2.go</code><br></p>
<pre><code>type eface struct {
        _type *_type //类型信息
        dataunsafe.Pointer //数据信息,指向数据指针
}
</code></pre>
<p><br>可以看到上面eface包含了2个元素,一个是_type,指向对象的类型信息,一个 data,数据指针<br></p>
<p></p>
<h2 id="三_type-结构体">三:_type 结构体</h2>
<p><br><code>_type</code> 位于 <code>src/runtime/type.go</code><br><br>
<br><code>_type</code> 是go里面所有类型的一个抽象,里面包含GC,反射,大小等需要的细节,它也决定了data如何解释和操作。<br>里面包含了非常多信息 类型的大小、哈希、对齐以及种类等自动。<br><br>
<br>所以不论是空<code>eface</code>和非空<code>iface</code>都包含 <code>_type</code> 数据类型<br></p>
<pre><code class="language-go">type _type struct {
        size       uintptr //数据类型共占用的空间大小
        ptrdata    uintptr //含有所有指针类型前缀大小
        hash       uint32//类型hash值;避免在哈希表中计算
        tflag      tflag   //额外类型信息标志
        align      uint8   //该类型变量对齐方式
        fieldalign uint8   //该类型结构字段对齐方式   
        kind       uint8   //类型编号
        alg      *typeAlg //算法表 存储hash和equal两个操作。map key便使用key的_type.alg.hash(k)获取hash值
        // gcdata stores the GC type data for the garbage collector.
        // If the KindGCProg bit is set in kind, gcdata is a GC program.
        // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
        gcdata    *byte   //gc数据
        str       nameOff      // 类型名字的偏移
        ptrToThis typeOff
}
</code></pre>
<br>
<br>_type 中的一些数据类型如下:<br>
<pre><code>// typeAlg is 总是 在 reflect/type.go 中 copy或使用.
// 并保持他们同步.
type typeAlg struct {
        // 算出该类型的Hash
        // (ptr to object, seed) -&gt; hash
        hash func(unsafe.Pointer, uintptr) uintptr
        // 比较该类型对象
        // (ptr to object A, ptr to object B) -&gt; ==?
        equal func(unsafe.Pointer, unsafe.Pointer) bool
}
type nameOff int32
type typeOff int32
</code></pre>
<p><br>但是各个类型需要的类型描叙是不一样的,比如chan,除了chan本身外,还需要描述其元素类型,而map则需要key类型信息和value类型信息等:<br></p>
<pre><code>//src/runtime/type.go

type ptrtype struct {
        typ_type
        elem *_type
}

type chantype struct {
        typ_type
        elem *_type
        diruintptr
}

type maptype struct {
        typ      _type
        key      *_type
        elem       *_type
        bucket   *_type // internal type representing a hash bucket
        keysize    uint8// size of key slot
        valuesizeuint8// size of value slot
        bucketsize uint16 // size of bucket
        flags      uint32
}
</code></pre>
<p><br>看上面的类型信息,第一个自动都是 <code>_type</code>,接下来也定义了一堆类型所需要的信息(如子类信息),这样在进行类型相关操作时,可通过一个字(typ *_type)即可表述所有类型,然后再通过_type.kind可解析出其具体类型,最后通过地址转换即可得到类型完整的”_type树”,参考reflect.Type.Elem()函数:<br></p>
<pre><code>// reflect/type.go
// reflect.rtype结构体定义和runtime._type一致type.kind定义也一致(为了分包而重复定义)
// Elem()获取rtype中的元素类型,只针对复合类型(Array, Chan, Map, Ptr, Slice)有效
func (t *rtype) Elem() Type {
        switch t.Kind() {
        case Array:
                tt := (*arrayType)(unsafe.Pointer(t))
                return toType(tt.elem)
        case Chan:
                tt := (*chanType)(unsafe.Pointer(t))
                return toType(tt.elem)
        case Map:
                tt := (*mapType)(unsafe.Pointer(t))
                return toType(tt.elem)
        case Ptr:
                tt := (*ptrType)(unsafe.Pointer(t))
                return toType(tt.elem)
        case Slice:
                tt := (*sliceType)(unsafe.Pointer(t))
                return toType(tt.elem)
        }
        panic("reflect: Elem of invalid type")
}
</code></pre>
<p></p>
<h2 id="四没有方法的interface赋值后内部结构">四:没有方法的interface赋值后内部结构</h2>
<p><br>对于没有方法的interface赋值后的内部结构是怎样的呢?<br>可以先看段代码:<br></p>
<pre><code>import (
        "fmt"
        "strconv"
)

type Binary uint64

func main() {
        b := Binary(200)
        any := (interface{})(b)
        fmt.Println(any)
}
</code></pre>
<p><br>输出200,赋值后的结构图是这样的:<br><img src="https://cdn.nlark.com/yuque/0/2019/jpeg/127300/1568960115777-a5449bd8-8639-4965-b1c0-6af826cfc79e.jpeg#align=left&amp;display=inline&amp;height=199&amp;originHeight=199&amp;originWidth=388&amp;size=0&amp;status=done&amp;style=none&amp;width=388" alt="" loading="lazy"></p>
<blockquote>
<p>图片来自:https://blog.csdn.net/i6448038/article/details/82916330</p>
</blockquote>
<p><br>对于将不同类型转化成type万能结构的方法,是运行时的<code>convT2E</code>方法,在runtime包中。<br>以上,是对于没有方法的接口说明。<br>对于包含方法的函数,用到的是另外的一种结构,叫iface<br></p>
<p></p>
<h2 id="五iface-非空接口">五:iface 非空接口</h2>
<p><br>iface结构体表示非空接口:<br>
</p>
<h3 id="iface">iface</h3>
<pre><code class="language-go">// runtime/runtime2.go
// 非空接口
type iface struct {
    tab*itab
    data unsafe.Pointer //指向原始数据指针
}
</code></pre>
<p></p>
<h3 id="itab">itab</h3>
<p><br>itab结构体是iface不同于eface,比较关键的数据结构<br></p>
<pre><code class="language-kotlin">// runtime/runtime2.go
// 非空接口的类型信息
type itab struct {
    //inter 和 _type 确定唯一的 _type类型
    inter*interfacetype    // 接口自身定义的类型信息,用于定位到具体interface类型
    _type*_type      // 接口实际指向值的类型信息-实际对象类型,用于定义具体interface类型
    hash int32          //_type.hash的拷贝,用于快速查询和判断目标类型和接口中类型是一致
    _   byte
    funuintptr //动态数组,接口方法实现列表(方法集),即函数地址列表,按字典序排序
                  //如果数组中的内容为空表示 _type 没有实现 inter 接口
                  
}
</code></pre>
<p><br>属性<code>interfacetype</code>类似于_type,其作用就是interface的公共描述,类似的还有<code>maptype</code>、<code>arraytype</code>、<code>chantype</code>…其都是各个结构的公共描述,可以理解为一种外在的表现信息。interfacetype源码如下:<br></p>
<pre><code class="language-go">// runtime/type.go
// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
   typ   _type
   pkgpath name
   mhdr    []imethod      // 接口方法声明列表,按字典序排序
}

// 接口的方法声明,一种函数声明的抽象
// 比如:func Print() error
type imethod struct {
   name nameOff          // 方法名
   ityp typeOff                // 描述方法参数返回值等细节
}

type nameOff int32
type typeOff int32
</code></pre>
<blockquote>
<p>method 存的是func 的声明抽象,而 itab 中的 fun 字段才是存储 func 的真实切片。</p>
</blockquote>
<p><br>非空接口(iface)本身除了可以容纳满足其接口的对象之外,还需要保存其接口的方法,因此除了<strong>data字段,iface通过<code>tab</code>字段描述非空接口的细节,包括接口方法定义,接口方法实现地址,接口所指类型</strong>等。iface是非空接口的实现,而不是类型定义,iface的真正类型为<code>interfacetype</code>,其第一个字段仍然为描述其自身类型的<code>_type</code>字段。<br></p>
<p></p>
<h2 id="六iface整体结构图">六:iface整体结构图</h2>
<p><br><img src="https://cdn.nlark.com/yuque/0/2019/jpeg/127300/1568960115779-4ac8ef27-c181-494e-93f4-f63de5b64177.jpeg#align=left&amp;display=inline&amp;height=1246&amp;originHeight=1246&amp;originWidth=1548&amp;size=0&amp;status=done&amp;style=none&amp;width=1548" alt="" loading="lazy"></p>
<blockquote>
<p>图片来自:https://blog.csdn.net/i6448038/article/details/82916330</p>
</blockquote>
<p></p>
<h2 id="七含有方法的interface赋值后的内部结构">七:含有方法的interface赋值后的内部结构</h2>
<p><br>含有方法的interface赋值后的内部结构是怎样的呢?<br></p>
<pre><code>package main

import (
        "fmt"
        "strconv"
)

type Binary uint64
func (i Binary) String() string {
        return strconv.FormatUint(i.Get(), 10)
}

func (i Binary) Get() uint64 {
        return uint64(i)
}

func main() {
        b := Binary(200)
        any := fmt.Stringer(b)
        fmt.Println(any)
}
</code></pre>
<p><br>首先,要知道代码运行结果为:200。<br>其次,了解到fmt.Stringer是一个包含String方法的接口。<br></p>
<pre><code>type Stringer interface {
        String() string
}
</code></pre>
<p><br>最后,赋值后接口Stringer的内部结构为:<br><img src="https://cdn.nlark.com/yuque/0/2019/jpeg/127300/1568960115948-fe5174b9-9dae-4da5-9c2f-e026d7204a1a.jpeg#align=left&amp;display=inline&amp;height=262&amp;originHeight=262&amp;originWidth=533&amp;size=0&amp;status=done&amp;style=none&amp;width=533" alt="" loading="lazy"><br></p>
<p></p>
<h2 id="八参考">八:参考:</h2>
<p>https://wudaijun.com/2018/01/go-interface-implement/<br>https://blog.csdn.net/i6448038/article/details/82916330#comments<br></p>


</div>
<div id="MySignature" role="contentinfo">
    == just do it ==<br><br>
来源:https://www.cnblogs.com/jiujuan/p/12653806.html
頁: [1]
查看完整版本: 深入理解Go语言(01): interface源码分析