slice 切片,也可以理解为动态数组。与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
1. 介绍
1.1 数据结构
1 | type SliceHeader struct { |
Data
是指向数组的指针;Len
是当前切片的长度;Cap
是当前切片的容量,即Data
数组的大小:

1.2 初始化
1 | slice := make([]int, 10) // 内存空间 = 切片中元素大小 × 切片容量 |
使用下标初始化切片不会拷贝原数组或者原切片中的数据,它只会创建一个指向原数组的切片结构体,所以修改新切片的数据也会修改原切片。
1 | func main() { |
2. 引用机制
2.1 不同初始化方式
1 | slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} |

2.2 修改引用
注意,向 s2
尾部追加一个元素 100
1 | s2 = append(s2, 100) |
s2
容量刚好够,直接追加。不过,这会修改原始数组对应位置的元素。这一改动,数组和 s1
都可以看得到。

2.3 扩容拷贝
再次向 s2
追加元素200:
1 | s2 = append(s2, 200) |
这时,s2
的容量不够用,该扩容了。于是,s2
另起炉灶,将原来的元素复制新的位置,扩大自己的容量。
并且为了应对未来可能的 append
带来的再一次扩容,s2
会在此次扩容的时候多留一些 buffer
,将新的容量将扩大为原始容量的2倍,也就是10了。

4.4 修改其他引用
最后,修改 s1
索引为2位置的元素:
1 | s1[2] = 20 |
这次只会影响原始数组相应位置的元素。它影响不到 s2
了,人家已经远走高飞了。
![s1[2]=20](/p/c35a2107/4.png)
再提一点,打印 s1
的时候,只会打印出 s1
长度以内的元素。所以,只会打印出3个元素,虽然它的底层数组不止3个元素。
3. 扩容机制
3.1 源代码
1 | func growslice(et *_type, old slice, cap int) slice { |
如果期望容量大于当前容量的两倍就会使用期望容量;
如果当前切片的长度小于 1024 就会将容量翻倍;
如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
3.2 cap不足重新分配
下面这个例子底层用的同一个数组, cap充足没有重新分配新数组
1 | func main() { |
下面这个例子, 底层用了两个数组, b扩容的时候, cap不足另起炉灶了。
1 | func main() { |
3.3 扩容时底层ptr指向新的地址
1 | func main() { |
可以看出, cap 是2倍增长的(<1024)
当cap变化的时候,ss 的指针变了。
&ss 的指针没有变,因为 &ss操作返回的是该切片的内部指针地址。
4. 头脑风暴
- cap小于1024的时候是2倍增长的,超过1024每次增加25%
- slice := make([]int, 0, 10),第三个参数是cap。