後來終於在眼淚中明白
有些人一旦錯過就不再
Go interface 底层实现
一、interface 到底是什么?
一句话:
interface 就是一个「盒子」,里面装了两个东西:
-
类型指针(这个盒子里装的到底是什么结构体 / 类型?)
-
数据指针(这个类型对应的真实数据在哪里?)
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 件事:
- 构造 itab
- 把 Buffer 类型填进去
- 把 Read () 方法地址填进去
- 把 数据指针指向真实 buffer
2. 方法调用流程
r.Read()
- 从 iface.tab 找到 itab
- 从 itab.fun 找到 Read 函数地址
- 直接 CALL 执行
3. 类型断言流程
buf, ok := r.(*bytes.Buffer)
- 取 iface.tab._type
- 和目标类型比较
- 一致 → 返回数据
- 不一致 → 返回失败
六、interface 三大特性底层原理
1. 为什么 nil 接口!= 有类型 nil?
var p *MyStruct
var i interface{} = p
- i 不是 nil!
- 因为 eface._type != nil
- 只有 _type=nil 且 data=nil 才是真 nil
2. 为什么断言快?
因为用 hash 比对,不是遍历方法列表。
3. 为什么接口能存任意类型?
因为只存 类型指针 + 数据指针,和类型无关。
七、总结
- 空接口:eface = 类型 + 数据
- 非空接口:iface = itab + 数据
- itab = 接口类型 + 真实类型 + 方法地址
- 方法调用:从 itab 取方法直接执行
- 断言:比对类型是否一致