基于 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 的大小取决于平台 :
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 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 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 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 func growslice (oldLen, newLen, cap int ) { newcap := cap doublecap := newcap + newcap if newLen > doublecap { newcap = newLen } else { if oldLen < 1024 { newcap = doublecap } else { 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修改前: ┌───┬───┬───┬───┬───┐ │ 1 │ 2 │ 3 │ 4 │ 5 │ ← a (len =5 , cap =5 ) └───┴───┴───┴───┴───┘ ↑___↑ b (len =2 , cap =4 ) b[0 ] = 99 修改后: ┌───┬────┬────┬───┬───┐ │ 1 │ 99 │ 3 │ 4 │ 5 │ ← a也被修改! └───┴────┴────┴───┴───┘
四、映射 map 4.1 底层结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type hmap struct { count int flags uint8 B uint8 noverflow uint16 hash0 uint32 buckets unsafe.Pointer oldbuckets unsafe.Pointer nevacuate uintptr extra *mapextra } type bmap struct { tophash [bucketCnt]uint8 }
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 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 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 Age int Sex bool }
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 type Bad struct { a bool b int64 c bool } type Good struct { b int64 a bool c bool }
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 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 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
字段顺序排列
对齐后大小
内存对齐、字段重排
理解底层实现有助于写出更高效的代码,特别是在性能敏感场景。