array
特征
- 内存连续,可以根据索引获取元素.
- 初始化之后大小就无法改变.
- 存储元素类型相同、大小相同的两个数组才是一个类型
初始化
arr1 := [3]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
特殊点
在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化,如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上
slice
结构
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
- Data 是指向数组中本切片第一个可用元素的指针;
- Len 是当前切片的长度;
- Cap 是当前切片的容量,即 Data 数组的大小.
Data 指向一片连续的内存空间,切片可以理解成一片连续的内存空间加上长度与容量的标识。
初始化
arr[0:3] or slice[0:3]
slice := []int{1, 2, 3}
slice := make([]int, 10)
- 通过下标的方式获得数组或者切片的一部分;
- 使用字面量初始化新的切片;
- 使用关键字 make 创建切片.
如果当前的切片不会发生逃逸并且切片非常小(<5)的时候,会在栈上分配内存; 如果切片长度较大,或者发生内存逃逸,则会在堆上进行内存分配。
追加与扩容
追加时,当切片的容量不足,发生扩容, 为切片分配新的内存空间并拷贝原切片中元素.
扩容策略
- 如果期望容量大于当前容量的两倍就会使用期望容量;
- 如果当前切片的长度小于 1024 就会将容量翻倍;
- 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
- 当数组中元素所占的字节大小为 1、8 或者 2 的倍数时,运行时会使用代码对齐内存, 获取的内存数量可能会稍微增多,提高内存的分配效率并减少碎片。
append结果会返回一个新的切片,其中包含了新的数组指针、大小和容量,这个返回的三元组最终会覆盖原切片
代码分析
var arr []int64
arr = append(arr, 1, 2, 3, 4, 5)
当我们执行上述代码时,会触发 runtime.growslice 函数扩容 arr 切片并传入期望的新容量 5,这时期望分配的内存大小为 40 字节;不过因为切片中的元素大小等于 sys.PtrSize,所以运行时会调用 runtime.roundupsize 向上取整内存的大小到 48 字节,所以新切片的容量为 48 / 8 = 6
切片拷贝
copy(dest, src)
在大切片上执行拷贝操作时一定要注意对性能的影响。