数据结构
type slice struct {
array unsafe.Pointer
len int
cap int
}
array指向底层数组,len表示切片长度,cap表示数组容量
slice的扩容机制
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
当cap < 1024:
每次扩容 *2
当cap >= 1024:
每次 * 1.25
append函数将元素追加到slice的末尾。如果有足够的容量,则将新元素追加进去。否则,将分配一个新的底层数组。append返回更新的切片。因此,有必要将append的结果存储在保存切片本身的变量中
slice copy
使用copy()内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值。
例如长度为10的切片拷贝到长度为5的切片时,将会拷贝5个元素。
也就是说,copy过程中不会发生扩容。
例子
func test(s[] int) {
s[0] = 100
}
func main() {
var s []int
for i := 0; i < 10; i ++ {
s = append(s, i)
}
test(s)
fmt.Println(s)
}
输出结果:
[100 1 2 3 4 5 6 7 8 9]
func test(s[] int) {
s = append(s, 1000)
s[0] = 100
fmt.Println("test里面s长度: ", len(s))
fmt.Println("test里面s容量: ", cap(s))
}
func main() {
var s []int
for i := 0; i < 10; i ++ {
s = append(s, i)
}
fmt.Println("test前长度: ", len(s))
fmt.Println("test前容量: ", cap(s))
test(s)
fmt.Println("test后长度: ", len(s))
fmt.Println("test后容量: ", cap(s))
fmt.Println(s)
}
test前长度: 10
test前容量: 16
test里面s长度: 11
test里面s容量: 16
test后长度: 10
test后容量: 16
[100 1 2 3 4 5 6 7 8 9]
注意:函数传递的是slice结构体的复制版本,它们的指针指向同一个底层数组地址,但是容量和长度的变化是各自不影响的,因此1000在外面没有打印出来
总结
1.预先分配内存避免切片扩容,可以提升性能。
2.扩容的时候如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍。
3.数组和 slice 在1000以内的容量上时性能一致,10000~1000000容量时数组的效率就比slice好了一倍,10000000容量往后数组性能大幅度下降,slice 是数组性能的两倍。