mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
3.2 KiB
3.2 KiB
Go 切片内部原理
来源:Effective Go
三项描述符
切片是一个运行时数据结构,包含三个组件:
- 指针:第一个可访问元素的地址
- 长度:元素数量(
len(s)) - 容量:到底层数组末尾的最大元素数(
cap(s))
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4] // s = [20, 30, 40]
// 指针:&arr[1],长度:3,容量:4
nil 切片的三项均为零/nil。
切片引用底层数组
切片不存储数据 — 它们描述数组的一部分:
data := [4]int{1, 2, 3, 4}
a := data[0:2] // [1, 2]
b := data[1:3] // [2, 3]
b[0] = 99
fmt.Println(a) // [1, 99] - 两者都看到变化
fmt.Println(data) // [1, 99, 3, 4]
切片运算符
s[lo:hi] 创建从索引 lo 到 hi-1 的切片:
s := []int{0, 1, 2, 3, 4, 5}
s[2:4] // [2, 3]
s[:3] // [0, 1, 2]
s[3:] // [3, 4, 5]
三索引形式 s[lo:hi:max] 将容量限制为 max-lo。
为什么 append 必须返回切片
切片头是按值传递的。函数可以修改元素但无法改变调用者的切片头:
func Append(slice, data []byte) []byte {
l := len(slice)
if l+len(data) > cap(slice) {
newSlice := make([]byte, (l+len(data))*2)
copy(newSlice, slice)
slice = newSlice // 只改变局部变量
}
slice = slice[0 : l+len(data)]
copy(slice[l:], data)
return slice // 调用者必须接收新的切片头
}
当发生重新分配时,slice 指向新数组。调用者的原始引用仍指向旧数组 — 返回使调用者能够更新其引用。
copy 函数
copy(dst, src) 复制元素并返回复制的数量:
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src) // n=3, dst=[1,2,3]
正确处理重叠切片。复制 min(len(dst), len(src)) 个元素 — 不会发生重新分配。
切片常见陷阱
1. 共享底层数组
original := []int{1, 2, 3, 4, 5}
subset := original[1:3]
subset[0] = 99
fmt.Println(original) // [1, 99, 3, 4, 5] - 被修改了!
// 修复:创建独立副本
subset := make([]int, 2)
copy(subset, original[1:3])
2. append 可能重新分配也可能不
a := make([]int, 3, 5) // len=3, cap=5
b := a[0:3]
a = append(a, 4) // 在容量内 - 仍然共享
a = append(a, 5, 6) // 超出容量 - 现在独立
3. 大底层数组导致内存泄漏
// 不好:小切片将整个文件保留在内存中
func getHeader(file []byte) []byte { return file[:100] }
// 好:复制以释放大数组
func getHeader(file []byte) []byte {
header := make([]byte, 100)
copy(header, file)
return header
}
4. nil vs 空切片
var nilSlice []int // nil, len=0, cap=0
emptySlice := []int{} // 非 nil, len=0, cap=0
// 两者在 len、cap、append、range 中表现相同
// 未初始化状态优先使用 nil
快速参考
| 操作 | 行为 |
|---|---|
s[lo:hi] |
从 lo 到 hi-1 的切片 |
s[lo:hi:max] |
容量限制为 max-lo 的切片 |
append(s, x...) |
返回新切片;可能重新分配 |
copy(dst, src) |
返回复制数量;不重新分配 |