2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, go-principle, interface]
|
|
|
|
|
|
create time: 2026-06-07 15:40
|
2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
# Interface 底层原理
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 概述
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
本文从 runtime 源码角度解析 Go 接口的两种内部表示:空接口(eface)和非空接口(iface),以及 itab 的创建、缓存机制。理解接口底层能让你写出更高效的类型断言代码,也能解释为什么 `nil interface` 不等于 `interface(nil)`。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!question] ❓ 思考
|
|
|
|
|
|
> 为什么 `var p *int = nil; var i io.Writer = p` 中 `i != nil`?空接口 `interface{}` 和非空接口在内存布局上有什么本质区别?itab 为什么需要缓存?
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 正文
|
|
|
|
|
|
|
|
|
|
|
|
### 一、空接口:eface
|
|
|
|
|
|
|
|
|
|
|
|
没有任何方法声明的接口就是空接口 `interface{}`:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type eface struct {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
_type *_type // 动态类型元数据
|
|
|
|
|
|
data unsafe.Pointer // 动态值(指向数据的指针)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
两个字段各 8 字节,共 16 字节。赋值前后对比:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart LR
|
|
|
|
|
|
Before["var e interface{}<br/>_type=nil, data=nil"] --> After["e = 42<br/>_type→int元数据, data→&42"]
|
|
|
|
|
|
style Before fill:#ffebee
|
|
|
|
|
|
style After fill:#e8f5e9
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
#### _type:类型的"身份证"
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type _type struct {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
size uintptr // 类型大小
|
|
|
|
|
|
ptrdata uintptr // 前缀含指针的字节数
|
|
|
|
|
|
hash uint32 // 类型的 hash 值
|
|
|
|
|
|
tflag tflag // 类型标志
|
|
|
|
|
|
align uint8 // 内存对齐
|
|
|
|
|
|
fieldAlign uint8
|
|
|
|
|
|
kind uint8 // 类型编号(struct/function/interface...)
|
|
|
|
|
|
equal func(unsafe.Pointer, unsafe.Pointer) bool // 比较函数
|
|
|
|
|
|
gcdata *byte
|
|
|
|
|
|
str nameOff
|
|
|
|
|
|
ptrToThis typeOff
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
`_type` 是 Go 所有类型的抽象基类——int、string、struct 等所有类型都对应一个 `_type` 实例。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 二、非空接口:iface + itab
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
包含方法列表的接口需要额外的结构来存储方法地址:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
type iface struct {
|
|
|
|
|
|
tab *itab // 接口类型信息 + 动态类型信息 + 方法地址
|
|
|
|
|
|
data unsafe.Pointer // 动态值
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type itab struct {
|
|
|
|
|
|
inter *interfacetype // 接口本身的描述(方法列表)
|
|
|
|
|
|
_type *_type // 实现类型的描述
|
|
|
|
|
|
hash uint32 // _type.hash 的副本,用于类型 switch
|
|
|
|
|
|
_ [4]byte
|
|
|
|
|
|
fun [1]uintptr // 可变长数组:接口方法的实际地址
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
用一个具体例子展示完整结构:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type Phone interface {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
Call()
|
|
|
|
|
|
SendMessage()
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type Apple struct { PhoneName string }
|
|
|
|
|
|
func (a Apple) Call() {}
|
|
|
|
|
|
func (a Apple) SendMessage() {}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var ifc Phone = Apple{PhoneName: "iphone"}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TB
|
|
|
|
|
|
ifc["iface<br/>tab → itab | data → &Apple"]
|
|
|
|
|
|
|
|
|
|
|
|
itab["itab"] --> inter["interfacetype<br/>Phone.Call, Phone.SendMessage"]
|
|
|
|
|
|
itab --> atype["_type<br/>Apple 的元数据"]
|
|
|
|
|
|
itab --> fun["fun[0]=Call_addr<br/>fun[1]=SendMessage_addr"]
|
|
|
|
|
|
|
|
|
|
|
|
apple["Apple 实例<br/>PhoneName='iphone'"] -.data指向.-> ifc
|
|
|
|
|
|
|
|
|
|
|
|
style ifc fill:#e3f2fd
|
|
|
|
|
|
style itab fill:#fff9c4
|
|
|
|
|
|
style inter fill:#e8f5e9
|
|
|
|
|
|
style apple fill:#fce4ec
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 源码要点
|
|
|
|
|
|
> itab.fun 是一个**可变长数组**。它保存的不是接口定义中的方法地址,而是具体类型(Apple)中对应方法的实际地址。这是通过求接口方法列表和具体类型方法列表的交集得到的。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 三、Itab 缓存:itabTable
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
每次给接口赋值都要查找或创建 itab 吗?不会。Go 用哈希表缓存所有已创建的 itab:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
type itabTableType struct {
|
|
|
|
|
|
size uintptr
|
|
|
|
|
|
count uintptr
|
|
|
|
|
|
entries [itabInitSize]*itab // 哈希表,2^11 = 2048 个槽位
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
查找流程:
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
Start["给接口赋值"] --> Hash{"itab 已在缓存?"}
|
|
|
|
|
|
Hash -->|是| Return["直接复用 existing itab"]
|
|
|
|
|
|
Hash -->|否| Create["创建新 itab:<br/>1. 填充 inter 和 _type<br/>2. 求方法交集 → fun[]<br/>3. CAS 插入缓存"]
|
|
|
|
|
|
Create --> Insert{"哈希冲突?"}
|
|
|
|
|
|
Insert -->|是| Quadratic["二次寻址法找空位"]
|
|
|
|
|
|
Insert -->|否| Store["存入计算出的槽位"]
|
|
|
|
|
|
Quadratic --> Store
|
|
|
|
|
|
Store --> Done["完成"]
|
|
|
|
|
|
Return --> Done
|
|
|
|
|
|
style Return fill:#e8f5e9
|
|
|
|
|
|
style Create fill:#fff9c4
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
哈希 key 的计算方式:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
|
|
|
|
|
|
return uintptr(inter.typ.hash ^ typ.hash)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
同类型的多次赋值只创建一个 itab:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var ifc Phone
|
|
|
|
|
|
ifc = Apple{...} // 创建 itab,缓存起来
|
|
|
|
|
|
ifc = Apple{...} // 直接从缓存取
|
|
|
|
|
|
ifc = Nokia{...} // 为 Nokia 创建新的 itab
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 四、常见陷阱:nil interface ≠ interface(nil)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var p *int = nil
|
|
|
|
|
|
var i io.Writer = p // i 的 data 是 nil,但 tab 不为 nil
|
|
|
|
|
|
fmt.Println(i == nil) // false!
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
原因:接口等于 nil 的判定条件是 `tab == nil && data == nil`。虽然 `data` 是 nil,但 `tab` 指向了一个有效的 itab(其中 fun 全为零),所以 `i != nil`。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
var i2 io.Writer = nil // 这才是真正的 nil interface
|
|
|
|
|
|
fmt.Println(i2 == nil) // true
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ 面试高频
|
|
|
|
|
|
> 这个知识点是 Go 面试中最常考的接口相关题目之一。核心结论:**接口由 (tab, data) 两个字段组成,只有两者都为 nil 时接口才等于 nil。**
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 五、性能影响
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
1. **接口赋值有轻微开销**:需要查找/创建 itab,但命中缓存后几乎无额外成本
|
|
|
|
|
|
2. **接口比较比类型比较慢**:需要先比较 `_type.hash`,再比较完整 `_type`
|
|
|
|
|
|
3. **避免不必要的接口转换**:频繁的类型断言会增加运行时开销
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 小结
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
- 空接口 `eface` 是 `(_type, data)` 二元组;非空接口 `iface` 多了 `itab`
|
|
|
|
|
|
- itab 包含接口类型、实现类型和方法地址,创建后缓存到 itabTable
|
|
|
|
|
|
- 接口判 nil 要看 tab 和 data 是否同时为 nil
|
|
|
|
|
|
- 同类型的接口赋值复用同一个 itab,首次创建有开销,后续命中缓存
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
## 关联笔记
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
- [[hzh/GolangStar/Go语言基础/Go语言接口]] — 接口的基础用法与隐式实现
|
|
|
|
|
|
- [[hzh/GolangStar/Go语言原理/interface原理]] — (同名文件)
|
|
|
|
|
|
- [[hzh/GolangStar/Go面试题库/Interface面试题]] — Interface 相关高频面试题
|