基于 Go 1.18 版本,分析基本数据类型的底层实现。

一、整型与浮点型

1.1 整型

整型在内存中是连续存储的,大小取决于类型:

1
2
3
4
5
6
7
┌─────────────────────────────────────────────────────┐
│ int8/uint8 [1字节] ████████ │
│ int16/uint16 [2字节] ████████████████ │
│ int32/uint32 [4字节] ████████████████████████████ │
│ int64/uint64 [8字节] ████████████████████████████ │
│ ████████████████████████████ │
└─────────────────────────────────────────────────────┘

int 和 uint 的大小取决于平台

  • 32位系统:4字节
  • 64位系统:8字节

1.2 浮点型

遵循 IEEE 754 标准:

1
2
3
4
5
6
7
8
9
10
11
float32 (32位):
┌────────┬────────────────────────────┐
│ 符号位 │ 指数位(8位) │ 尾数位(23位) │
│ 1bit │ 8bits │ 23bits │
└────────┴────────────────────────────┘

float64 (64位):
┌────────┬─────────────────────────────┬─────────────────────────────┐
│ 符号位 │ 指数位(11位) │ 尾数位(52位) │
│ 1bit │ 11bits │ 52bits │
└────────┴─────────────────────────────┴─────────────────────────────┘

二、字符串 string

2.1 底层结构

1
2
3
4
5
// runtime/string.go
type stringStruct struct {
str unsafe.Pointer // 指向字节数组的指针
len int // 字节长度
}
1
2
3
4
5
6
7
8
9
10
11
12
字符串 "hello" 的内存布局:

stringStruct
┌─────────────┬─────────────┐
│ str (指针) │ len = 5 │
└──────┬──────┴─────────────┘


┌─────┬─────┬─────┬─────┬─────┐
│ 'h' │ 'e' │ 'l' │ 'l' │ 'o' │
└─────┴─────┴─────┴─────┴─────┘
[0] [1] [2] [3] [4]

2.2 关键特性

不可变性:字符串内容存储在只读段,修改会触发新分配。

1
2
s := "hello"
s[0] = 'H' // 编译错误: cannot assign to s[0]

零拷贝切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
s := "hello world"
sub := s[0:5] // 不复制数据,共享底层字节

s
┌───────────┬───────────┐
│ ptr ──────┼──►│ len=11
└───────────┴───────────┘


┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ h │ e │ l │ l │ o │ │ w │ o │ r │ l │ d │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘

sub (s[0:5])
┌───────────┬───────────┐
│ ptr ──────┼──►│ len=5// 同一个指针,不同长度
└───────────┴───────────┘

三、切片 slice

3.1 底层结构

1
2
3
4
5
6
// runtime/slice.go
type slice struct {
ptr unsafe.Pointer // 指向底层数组
len int // 元素个数
cap int // 容量
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
切片 s := make([]int, 3, 5) 的内存布局:

slice
┌──────────┬──────────┬──────────┐
│ ptr │ len = 3 │ cap = 5 │
└────┬─────┴──────────┴──────────┘


┌────────┬────────┬────────┬────────┬────────┐
│ 0 │ 0 │ 0 │ 未使用 │ 未使用 │
└────────┴────────┴────────┴────────┴────────┘
[0] [1] [2] [3] [4]
↑________________↑ ↑________________↑
len=3 可访问 cap=5 可扩展

3.2 切片扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 扩容规则 (Go 1.18)
func growslice(oldLen, newLen, cap int) {
newcap := cap
doublecap := newcap + newcap
if newLen > doublecap {
newcap = newLen
} else {
if oldLen < 1024 {
newcap = doublecap // 翻倍
} else {
// 每次增加 1/4
for newcap < newLen {
newcap += newcap / 4
}
}
}
// 内存对齐后实际分配可能更大
}
1
2
3
4
5
6
7
8
9
10
11
12
扩容示例:

[]int{1,2,3} (len=3, cap=3)

│ append 1个元素

[]int{1,2,3,4} (len=4, cap=6) // 翻倍 → 6


│ append 3个元素 (超过cap)

[]int{1,2,3,4,5,6,7} (len=7, cap=12) // 翻倍 → 12

3.3 切片共享底层数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a := []int{1, 2, 3, 4, 5}
b := a[1:3]

a修改前:
┌───┬───┬───┬───┬───┐
12345 │ ← a (len=5, cap=5)
└───┴───┴───┴───┴───┘
↑___↑
b (len=2, cap=4)

b[0] = 99 修改后:
┌───┬────┬────┬───┬───┐
199345 │ ← a也被修改!
└───┴────┴────┴───┴───┘

四、映射 map

4.1 底层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// runtime/map.go
type hmap struct {
count int // 元素个数
flags uint8 // 状态标志
B uint8 // 桶数量的对数 (2^B个桶)
noverflow uint16 // 溢出桶数量
hash0 uint32 // 哈希种子
buckets unsafe.Pointer // 桶数组指针
oldbuckets unsafe.Pointer // 扩容时的旧桶
nevacuate uintptr // 扩容进度
extra *mapextra // 溢出桶信息
}

type bmap struct {
tophash [bucketCnt]uint8 // 每个桶8个元素的高8位哈希
// 后面跟着 keys、values、overflow指针
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
map 内部结构:

hmap
┌─────────┬───────┬─────┬──────────┬─────────────────────┐
│ count=5 │ flags │ B=2 │ hash0 │ buckets ────────────┼──┐
└─────────┴───────┴─────┴──────────┴─────────────────────┘ │

bucket数组 (2^B = 4 个桶) ▼
┌───────────────────────────────────────────────────────────┐
│ bucket[0] │ bucket[1] │ bucket[2] │ bucket[3] │
└─────┬─────┴─────┬─────┴─────┬─────┴─────┬─────┘ │
│ │ │ │ │
▼ ▼ ▼ ▼ │
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│tophash │ │tophash │ │tophash │ │tophash │ │
│[8]uint8│ │[8]uint8│ │[8]uint8│ │[8]uint8│ │
├────────┤ ├────────┤ ├────────┤ ├────────┤ │
│ keys[] │ │ keys[] │ │ keys[] │ │ keys[] │ │
├────────┤ ├────────┤ ├────────┤ ├────────┤ │
│values[]│ │values[]│ │values[]│ │values[]│ │
├────────┤ ├────────┤ ├────────┤ ├────────┤ │
│overflow┼──┼────────┼──┼────────┼──┼────────┤ (可能指向溢出桶)
└────────┘ └────────┘ └────────┘ └────────┘

4.2 查找流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
查找 map[key]

1. 计算 hash := hashFunc(key) + hash0
2. 取低B位确定桶: bucket = hash & (2^B - 1)
3. 取高8位快速比对: tophash = hash >> 56

hash: 0x7F3A2B1C4D5E6F80

┌────────────┴────────────┐
▼ ▼
低B位(桶索引) 高8位(tophash)
──────────── ──────────────
0x...F80 & 0b11 = 0 0x7F (用于桶内快速比对)

桶内查找:
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│top: │ 7F │ 3A │ -- │ -- │ 7F │ -- │ -- │ -- │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│key: │ k1 │ k2 │ │ │ k5 │ │ │ │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│val: │ v1 │ v2 │ │ │ v5 │ │ │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑
tophash=7F匹配 tophash=7F匹配
进一步比对key 进一步比对key

4.3 扩容机制

触发条件

  • 负载因子 > 6.5 (元素太多)
  • 溢出桶过多 (元素分布不均)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
扩容过程:

1. 等量扩容 (溢出桶多,整理数据)
oldbuckets → buckets (同样大小,重新整理)

2. 增量扩容 (元素多,容量翻倍)
oldbuckets (2^B) → buckets (2^(B+1))

扩容是渐进式的:
┌───────────────────────────────────────────┐
│ oldbuckets (正在迁移) │
│ ┌────┬────┬────┬────┐ │
│ │ ✓ │ ✓ │迁移中│ │ ← 逐个迁移 │
│ └────┴────┴────┴────┘ │
└───────────────────────────────────────────┘


┌───────────────────────────────────────────┐
│ buckets (新) │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │
│ │ ✓ │ ✓ │ │ │ │ │ │ │ │
│ └────┴────┴────┴────┴────┴────┴────┴────┘ │
└───────────────────────────────────────────┘

五、通道 channel

5.1 底层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// runtime/chan.go
type hchan struct {
qcount uint // 队列中元素个数
dataqsiz uint // 环形队列大小
buf unsafe.Pointer // 环形队列指针
elemsize uint16 // 元素大小
closed uint32 // 是否关闭
elemtype *_type // 元素类型
sendx uint // 发送索引
recvx uint // 接收索引
recvq waitq // 接收等待队列
sendq waitq // 发送等待队列
lock mutex // 互斥锁
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
有缓冲 channel: ch := make(chan int, 4)

hchan
┌─────────────────────────────────────────────────────┐
│ qcount=2 │ dataqsiz=4 │ buf ──────────────────┐ │
│ elemsize=8 │ closed=0 │ sendx=2 │ recvx=0 │ │
│ recvq ──┐ │ sendq ──┐ │ lock │ │
└─────────┼───────────┼────────────────────────┼─────┘
│ │ │
│ │ ▼
│ │ 环形队列 buf
│ │ ┌───┬───┬───┬───┐
│ │ │ 1 │ 2 │ │ │
│ │ └───┴───┴───┴───┘
│ │ ↑ ↑
│ │ recvx sendx
│ │ [0] [2]
│ │
▼ ▼
recvq sendq
(空) (空)

当 recvq 非空时:
recvq → ┌─────┐ ┌─────┐
│goroutine│ → │goroutine│
│sudog │ │sudog │
└─────┘ └─────┘

5.2 发送接收流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
发送 ch <- x:

┌─────────────────────────────────────────┐
│ 1. 加锁 │
└────────────────┬────────────────────────┘

┌─────────────────────────────────────────┐
│ 2. 检查 recvq 是否有等待者 │
│ 有 → 直接把数据拷贝给等待者,唤醒它 │
│ 解锁返回 │
└────────────────┬────────────────────────┘
▼ (没有等待者)
┌─────────────────────────────────────────┐
│ 3. 检查 buf 是否有空位 │
│ 有 → 写入 buf[sendx], sendx++ │
│ 解锁返回 │
└────────────────┬────────────────────────┘
▼ (没有空位)
┌─────────────────────────────────────────┐
│ 4. 当前 goroutine 加入 sendq │
│ 解锁, 进入等待状态 │
└─────────────────────────────────────────┘

六、接口 interface

6.1 空接口 eface

1
2
3
4
5
// runtime/runtime2.go
type eface struct {
_type *_type // 类型信息
data unsafe.Pointer // 数据指针
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
空接口 var i interface{} = "hello"

eface
┌─────────────┬─────────────┐
│ _type │ data │
└──────┬──────┴──────┬──────┘
│ │
▼ ▼
_type ┌─────┬─────┬─────┬─────┬─────┐
┌────────┐ │ 'h' │ 'e' │ 'l' │ 'l' │ 'o' │
│ string │ └─────┴─────┴─────┴─────┴─────┘
│ size=16│
│ ... │
└────────┘

6.2 非空接口 iface

1
2
3
4
5
6
7
8
9
10
11
12
type iface struct {
tab *itab // 接口表
data unsafe.Pointer // 数据指针
}

type itab struct {
inter *interfacetype // 接口类型
_type *_type // 实际类型
hash uint32 // 类型哈希
_ [4]byte // 填充
fun [1]uintptr // 方法表
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
非空接口 var r io.Reader = &os.File{}

iface
┌─────────────┬─────────────┐
│ tab │ data │
└──────┬──────┴──────┬──────┘
│ │
▼ ▼
itab os.File实例
┌─────────────┐ ┌───────────┐
│ inter │ │ fd │
│ (io.Reader) │ │ name │
├─────────────┤ │ ... │
│ _type │ └───────────┘
│ (*os.File) │
├─────────────┤
│ fun[0] ─────┼───► os.File.Read()
│ fun[1] ─────┼───► os.File.Write()
│ ... │
└─────────────┘

七、结构体 struct

7.1 内存布局

1
2
3
4
5
type Person struct {
Name string // 16字节
Age int // 8字节
Sex bool // 1字节
}
1
2
3
4
5
6
7
8
9
10
11
12
13
内存对齐 (64位系统):

Person 结构体
┌────────────────────────────────────┐
│ Name.ptr (8字节) │
├────────────────────────────────────┤
│ Name.len (8字节) │
├────────────────────────────────────┤
│ Age (8字节) │
├────────────────────────────────────┬┤
│ Sex (1字节) ││ padding (7字节)
└────────────────────────────────────┴┘
总大小: 16 + 8 + 1 + 7(padding) = 32字节

7.2 字段重排优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 未优化: 32字节
type Bad struct {
a bool // 1 + 7 padding
b int64 // 8
c bool // 1 + 7 padding
}

// 优化后: 16字节
type Good struct {
b int64 // 8
a bool // 1
c bool // 1
// 6 padding
}
1
2
3
4
5
6
7
8
Bad (32字节):              Good (16字节):
┌───┬───────────────┐ ┌──────────────┐
│ a │ padding │ │ b │
├───┴───────────────┤ ├───┬───┬──────┤
│ b │ │ a │ c │padding│
├───┬───────────────┤ └───┴───┴──────┘
│ c │ padding │
└───┴───────────────┘

八、指针 pointer

8.1 指针类型

1
2
var p *int      // 指向int的指针
var pp **int // 指向指针的指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
指针内存布局:

指针 p (64位系统):
┌─────────────────────────────────────┐
│ 内存地址 (8字节) │
└──────────────┬──────────────────────┘


┌─────────┐
│ int值 │
└─────────┘

二级指针 pp:
┌───────────┐
│ pp │
└─────┬─────┘


┌───────────┐ ┌─────────┐
│ p (*int) │─────►│ int值 │
└───────────┘ └─────────┘

8.2 unsafe.Pointer

1
2
3
4
5
6
// unsafe.Pointer 是通用指针类型,可与任何指针互转
var x int = 42
p := unsafe.Pointer(&x)

// 转换为其他类型指针
px := (*int64)(p)

九、总结

类型 底层结构 大小 特点
string ptr + len 16字节 不可变、零拷贝切片
slice ptr + len + cap 24字节 动态数组、共享底层
map hmap + bmap 可变 哈希表、渐进扩容
channel hchan 可变 环形队列 + 等待队列
interface eface/iface 16字节 类型信息 + 数据指针
struct 字段顺序排列 对齐后大小 内存对齐、字段重排

理解底层实现有助于写出更高效的代码,特别是在性能敏感场景。