1. 底层
-  runtime/slice.go type slice struct {array unsafe.Pointer // 指向底层数组len int // 切片元素数量cap int // 底层数组容量 }
-  reflect/value.go type SliceHeader struct {Data uintptrLen intCap int }
2. 创建
-  根据数组创建 s := arr[0:3]
-  字面量:编译时插入创建数组的代码 s := []int{1, 2, 3}
-  make:运行时创建数组 s := make([]int, 10)
3. 测试
3.1 字面量创建切片底层
func main() {s := []int{1, 2, 3}fmt.Println(s)
}
查看 Plan9 汇编代码,运行:
go build -gcflags -S main.go
重点关注 s := []int{1,2,3} 对应的部分:
LEAQ    type.[3]int(SB), AX						#创建一个大小为3,类型为int的数组
PCDATA  $1, $0
NOP
CALL    runtime.newobject(SB)				  #新建一个结构体(slice)的值,并往里面塞3个数组
MOVQ    $1, (AX)
MOVQ    $2, 8(AX)
MOVQ    $3, 16(AX)
3.2 make 创建切片
func main() {s := make([]int, 3)fmt.Println(s)
}
查看 Plan9 汇编代码,运行:
go build -gcflags -S main.go
重点关注 s := make([]int, 3) 对应的部分:
LEAQ    type.int(SB), AX
MOVL    $3, BX
MOVQ    BX, CX
PCDATA  $1, $0
CALL    runtime.makeslice(SB) 		#直接调用 makeslice 方法
4. 访问
- 下标访问
- range 遍历
- len 查看切片长度
- cap 查看数组容量
5. 追加
s := []int{1, 2, 3}
s = append(s, 4, 5)
如果 append 后,len > cap,则需要做扩容。
6. 扩容
底层调用 growslice() 方法:
6.1 Go1.17 及之前的 growslice()
func growslice(et *_type, old slice, cap int) slice {// 检查...// 确定新 capnewcap := old.capdoublecap := newcap + newcapif cap > doublecap {// ① 如果新 cap 大于两倍旧 cap,则直接使用新 capnewcap = cap} else {// ② 如果新 cap 小于两倍旧 cap 且旧 cap 小于 1024,则 cap 直接翻倍if old.cap < 1024 {newcap = doublecap} else {// ③ 如果新 cap 小于两倍旧 cap 且旧 cap 大于 1024,则每次增长 25%for 0 < newcap && newcap < cap {newcap += newcap / 4}if newcap <= 0 {newcap = cap}}}// 新建数组,复制,字节对齐....
}
6.2 Go1.18 的 growslice()
func growslice(et *_type, old slice, cap int) slice {// 检查...// 确定新 capnewcap := old.capdoublecap := newcap + newcapif cap > doublecap {// ① 如果新 cap 大于两倍旧 cap,则直接使用新 capnewcap = cap} else {const threshold = 256if old.cap < threshold {// ② 如果新 cap 小于两倍旧 cap 且旧 cap 小于 256,则 cap 直接翻倍newcap = doublecap} else {// ③ 如果新 cap 小于两倍旧 cap 且旧 cap 大于 256,则增加幅度逐渐从 2x 降到 1.25x// 原始容量  			扩容系数//	 256					2.0//	 512          1.63//   1024         1.44//   2048         1.35//   4096         1.30for 0 < newcap && newcap < cap {newcap += (newcap + 3*threshold) / 4}...}}// 新建数组,复制,字节对齐....
}
总结
- Go1.17 及以前 - 如果 newcap 大于 2*oldcap,则直接使用 newcap
- 否则 - 如果 oldcap < 1024,则 2*oldcap
- 如果 oldcap >= 1024,则 1.25*oldcap
 
 
- Go1.18 - 如果 newcap 大于 2*oldcap,则直接使用 newcap
- 否则 - 如果 oldcap < 256,则 2*oldcap
- 如果 oldcap >= 256,则 oldcap += (oldcap + 3*256) / 4
 
 
在 Go1.18 中,优化了切片在容量较大时扩容的策略,让底层数组大小的增长更加平滑:通过减小阈值并固定增加一个常数,使得优化后的扩容的系数在阈值前后不再会出现从 2 到 1.25 的突变。该 commit 作者给出了几种原始容量下对应的“扩容系数”:
| 原始容量 | 扩容系数 | 
|---|---|
| 256 | 2.0 | 
| 512 | 1.63 | 
| 1024 | 1.44 | 
| 2048 | 1.35 | 
| 4096 | 1.30 | 
PS:slice 在扩容的时候是并发不安全的,在并发访问的时候,需要加锁。