go从零单排之interface

0 阅读3分钟

後來終於在眼淚中明白
有些人一旦錯過就不再

image.png

Go interface 底层实现

一、interface 到底是什么?

一句话:

interface 就是一个「盒子」,里面装了两个东西:

  1. 类型指针(这个盒子里装的到底是什么结构体 / 类型?)

  2. 数据指针(这个类型对应的真实数据在哪里?)

Go 接口分两种:

  • 空接口 interface {} :啥都能装,最小结构

  • 非空接口(带方法的接口) :带方法列表,要校验类型是否实现接口


二、底层核心结构体

位置:runtime/runtime2.go

1. 空接口 eface (interface {})

最简单,只存类型 + 数据

// 空接口:什么方法都没有的 interface{}
type eface struct {
    _type *_type      // 1. 类型信息:是什么类型(int/string/自定义结构体)
    data  unsafe.Pointer // 2. 数据指针:指向真实数据的内存地址
}

2. 非空接口 iface (带方法的接口)

带方法列表,会做方法实现校验

// 非空接口:有方法的接口,比如 io.Reader
type iface struct {
    tab  *itab  // 1. 接口表:存【接口类型】+【真实类型】+【方法指针列表】
    data unsafe.Pointer // 2. 数据指针:指向真实数据
}
// itab = 接口核心:接口与真实类型的“对应表”
type itab struct {
    inter *interfacetype // 这个接口自己的类型(比如 io.Reader)
    _type *_type         // 真实类型(比如 bytes.Buffer)
    hash  uint32         // 类型哈希,用于断言快速匹配
    fun   [1]uintptr     // 方法指针数组:存实现的方法地址(动态派发)
}

3. 类型结构体 _type

所有类型的元信息:

type _type struct {
    size       uintptr    // 类型大小
    ptrdata    uintptr    // 指针数据大小
    hash       uint32     // 类型哈希
    tflag      tflag      // 类型标记
    align      uint8      // 对齐
    kind       uint8      // 类型种类:int/string/struct/interface 等
}

三、白话总结

  • 空接口 = 类型 + 数据
  • 非空接口 = 接口表 (itab) + 数据
  • itab 最重要:itab 里存了这个类型到底实现了哪些方法
  • 断言:就是比对 _type 或 itab 是否匹配

四、interface 底层流程图

graph TD
    A[定义变量 var x io.Reader] --> B[创建空 interface 占位]
    B --> C[赋值 x 为 bytes.NewBufferString]
    C --> D[构建 itab 接口表]
    D --> E[itab.inter 为 io.Reader 类型]
    D --> F[itab._type 为 bytes.Buffer 类型]
    D --> G[itab.fun 填充 Read 方法地址]
    E --> H[构建 iface 结构]
    F --> H
    G --> H
    H --> I[iface.data 指向 buffer 实例]
    
    I --> J[调用 x.Read]
    J --> K[从 itab.fun 取方法指针]
    K --> L[直接跳转到方法执行]
    
    %% 断言流程
    I --> M[类型断言 x 是 bytes.Buffer]
    M --> N[比对 itab._type 是否一致]
    N --> O{一致?}
    O -->|是| P[返回数据指针]
    O -->|否| Q[panic 或 返回 ok=false]

五、核心流

1. 赋值流程

var r io.Reader
r = bytes.NewBuffer(...)

底层做了 3 件事:

  1. 构造 itab
  1. Buffer 类型填进去
  1. Read () 方法地址填进去
  1. 数据指针指向真实 buffer

2. 方法调用流程

r.Read()
  1. 从 iface.tab 找到 itab
  1. 从 itab.fun 找到 Read 函数地址
  1. 直接 CALL 执行

3. 类型断言流程

buf, ok := r.(*bytes.Buffer)
  1. 取 iface.tab._type
  1. 和目标类型比较
  1. 一致 → 返回数据
  1. 不一致 → 返回失败

六、interface 三大特性底层原理

1. 为什么 nil 接口!= 有类型 nil?

var p *MyStruct
var i interface{} = p
  • i 不是 nil!
  • 因为 eface._type != nil
  • 只有 _type=nil 且 data=nil 才是真 nil

2. 为什么断言快?

因为用 hash 比对,不是遍历方法列表。

3. 为什么接口能存任意类型?

因为只存 类型指针 + 数据指针,和类型无关。


七、总结

  1. 空接口:eface = 类型 + 数据
  1. 非空接口:iface = itab + 数据
  1. itab = 接口类型 + 真实类型 + 方法地址
  1. 方法调用:从 itab 取方法直接执行
  1. 断言:比对类型是否一致