[三步]带你理解Go语言的切片扩容
一、切片
首先我们要知道切片的本质是什么?
Slice的底层数据结构(src/runtime/slice.go):
type slice struct { array unsafe.Pointer // 指向底层数组的指针 len int // 当前切片长度 cap int // 底层数组容量 }
切片头(slice header) +---------------------------+ | array -> 底层数组 | | len:当前长度 | | cap:最大容量 | +---------------------------+ | v 底层数组(underlying array) +------+------+------+------+------+ | 1 | 2 | 3 | ? | ? | +------+------+------+------+------+ ^ ^ | | 起始元素 容量上限
而你手上有两张照片去放到这个相册里面 //这两张照片就是长度(len:2)
最后底层数据就是相片册类型的结构.
nums := make([]int{0,3})// 创建一个切片,len=0, cap=3 num = append(nums, 1) //len=1, cap=3 num = append(nums, 2) //len=2, cap=3 num = append(nums, 3) //len=3, cap=3 num = append(nums, 4) //len=4, len > cap 触发扩容
当执行 append操作时:
-
如果当前容量(cap)足够:直接在原底层数组末尾追加元素,修改
len并返回原切片(无新内存分配) -
如果容量不足:触发扩容逻辑,分配新的更大的底层数组,拷贝旧数据,再追加新元素
在 Go 语言 v1.18 版本之前,当执行append向 slice 底层数组追加数据。若旧容量(oldCap)小于所需的最小容量(即cap < len+num)时,会触发扩容操作,其扩容规则:
| 新容量计算规则 | |
|---|---|
| 新容量 = 旧容量 × 2(按2倍扩容) | |
| cap ≥ 1024 |
v1.18+扩容策略(
用append向slice底层数组追加num个数据。当cap>= len+num时,直接在slice底层对应的数组进行操作。如果cap< len+num需要扩容:
若 newLen > 2 * oldCap,直接将容量扩容至 newLen。(newlen = len + num)
若 oldCap < 256,将容量扩容至 2 * oldCap。
若 oldCap >= 256,每次扩容为 oldcap + (oldCap + 3 * 256)/4,重复此操作直到 newcap >= newLen。
若扩容后容量溢出,则返回 newLen;否则返回计算后的 newcap。溢出会在后续逻辑触发 panic,不在此函数处理。
这里举个例子:
a := make([]int,300,300) a = append(a,12) fmt.Println(len(a),cap(a))
输出结果是什么?
*
*
输
出
结
果
*
*
*
>>301 567//len(a),cap(a)
你做对了吗?按照v1.18+的扩容机制 ,300(oldcap) > 256, 所以newcap = 300 + (300 + 3 * 256)/4 = 567。
有的人可能会说,切片扩容不是按照那个1024的阈值去判断扩容2倍/1.25倍吗?其实说的也是对的,只是依赖版本不同,切片的扩容规则也就不一样了。
但本质上版本切片扩容的逻辑就是减少扩容次数,避免浪费内存空间。
四、总结
1. 理解切片的本质
2. 清楚切片扩容的触发条件:len + 溢出数 > cap
3. 根据切片扩容机制,进行扩容:
Gov1.18前:以 1024 为阈值:cap<1024 时扩容 2 倍,cap≥1024 时扩容 1.25 倍。
Gov1.18+:1. 若 newLen > 2oldCap → 直接扩容至 newLen(len + 溢出部分);
2. 若 oldCap < 256 → 扩容至 2oldCap;
3. 若 oldCap ≥ 256 → 每次扩容为 oldCap + (oldCap + 3*256)/4,重复至满足条件;

浙公网安备 33010602011771号