切片数据结构
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
- Data: 指向底层数组的指针
- Len: 当前切片的长度
- Cap 当前切片的容量
切片初始化
当切片发生逃逸或者非常大时,运行时需要在堆上初始化切片,如果当前的切片不会发生逃逸且切片非常小,make([]int,3,4)会被直接转换成
var arr [4]int
n:= arr[:3]
编译器会在栈上或者静态存储区创建数组
申请内存
runtime.makeslice在最后运行runtime.mallocgc 申请内存,如果遇到了比较小的对象会直接初始化在Go语言调度器里面的P结构,而大于32kb的对象会在堆上初始化。
切片追加和扩容
追加过程:
- 解构切片结构体获取数组指针、大小和容量,如果在追加元素后切片的大小大于容量,会调用runtime.groslice对切片进行扩容再将新的元素依次加入切片
- 如果使用 slice=append(slice,1,2,3),那么append后的切片会覆盖原切片。
扩容过程
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
const threshold = 256
if old.cap < threshold {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
// Transition from growing 2x for small slices
// to growing 1.25x for large slices. This formula
// gives a smooth-ish transition between the two.
newcap += (newcap + 3*threshold) / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
根据切片的当前容量选择不同的策略进行扩容
- 如果期望容量大于当前容量的两倍就会使用期望容量
- 否则如果当前切片的长度小于256就会将容量翻倍
- 如果当前切片的长度大于256就会每次增加(原容量+3*256)/4的容量,直到新容量大于期望容量 如果切片中元素不是指针类型,那么会将超出切片当前长度的位置清空并将原数组内存中的内容拷贝到新申请的内存中。
拷贝切片
- 在编译期间调用copy:copy函数会调用runtime.memove负责拷贝内存
- 在运行期间调用copy,编译器会使用runtime.slicecopy替换运行期间调用的copy 无论是编译期间拷贝还是运行时拷贝,两种拷贝方式都会通过runtime.memmove将整块内存的内容拷贝到目标的内存区域中
slice之间的比较
和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较。
为何slice不直接支持比较运算符呢?这方面有两个原因。
第一个原因,一个slice的元素是间接引用的,一个slice甚至可以包含自身(译注:当slice声明为[]interface{}时,slice的元素可以是自身)