go 切片
在go 语言的世界中,切片是一个很常用的数据结构,同时也有很多的坑,在面试中十个有八个面试官会问到,本文主要整理下切片数据结构、创建方式、扩容方式、常见面试题等。
slice 又称动态数组,依托数组实现,可以方便地进行扩容、传递等,实际使用中比数组更加灵活。
切片与底层数组共享一个内存地址,当切片的值改变时,会影响底层数组的值也一起改变,当切片扩容时会重新申请内存地址,此时切片不会影响底层数组
slice 依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重新分配并生成新的slice。
数据结构
type slice struct {
array unsafe.Pointer // 指向底层数组的地址
len int // 长度
cap int // 容量
}
创建切片
- 使用make
使用make 创建slice ,可以指定长度和容量,创建时底层会分配一个数组,数组的长度即容量
例如slice :=make([]int,5,10)
,底层指向的就是一个长度为10的数组,该slice 长度为5 ,可以使用slice[0]~slice[4]来操作元素,capacity为10,表示后续想slice添加新元素时不必重新分配内存,直接使用预留内存
- 使用数组创建
使用数组创建slice ,slice与原数组共用一部分内存
slice:=array[5:7]
切片从数组array[5]开始到array[7]结束,切片长度为2,数组后面的内容都作为切片的预留内存
slice 扩容
使用append 想slice 追加元素时,如果slice 空间不足,将会触发slice 扩容,扩容实际上就是重新分配一个更大的内存,将原slice 的数据拷贝进新的slice ,然后返回新的slice
扩容原则
- 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍
- 如果原slice 大于等于1024 扩大为原来的1.25倍
append 的实现步骤
- 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
- 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
- 将新元素追加进新Slice,Slice.len++,返回新的Slice。
slice copy
使用copy()内置函数拷贝两个切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值。
例如长度为10的切片拷贝到长度为5的切片时,将会拷贝5个元素,也就是说copy 的过程中不会发生扩容
// 可以使用这个特性来比较两个数的大小
min= copy(make([]struct{},a),make([]struct{},b))
特殊切片
根据数组或者切片生成新切片一般使用slice:=array[start:end]
,这种新生成的切片没有指定容量,实际上新切片容量为从start 开始到array 结束
比如如下两个切片,长度和容量都是一致的,使用共同的内存地址
sliceA := make([]int,5,10)
sliceB := sliceA[0:5]
根据数组或者切片生成新切片还有另一种写法,可以指定新切片的容量slice := array[start:end:cap]
,cap不能超过原切片的实际值
sliceA := make([]int,5,10)
sliceB := sliceA[0:5]
sliceC := sliceA[0:5:5]
总结
- 创建切片时可根据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能;
- 切片拷贝时需要判断实际拷贝的元素个数
- 谨慎使用多个切片操作同一个数组,以防读写冲突
- 每个切片都指向一个底层数组
- 每个切片都保存了当前切片的长度、底层数组可用容量
- 使用len()计算切片长度时间复杂度为O(1),不需要遍历切片
- 使用cap()计算切片容量时间复杂度为O(1),不需要遍历切片
- 通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而已
- 使用append()向切片追加元素时有可能触发扩容,扩容后将会生成新的切片
面试题
package main
import "fmt"
func main() {
{
test := []int{1, 2, 3, 4, 5}
change2(test)
fmt.Println(test)
}
}
func change2(param []int) {
param = append(param, 6)
}
输出结果为 1,2,3,4,5
解析:change2 对切片进行了扩容操作,param 是一个函数内的局部变量已经指向了新的内存地址,test指向的底层数组不变
package main
import "fmt"
func main() {
{
test := []int{1, 2, 3, 4, 5}
change(test)
fmt.Println(test)
}
}
func change(param []int) {
param = append(param[:1], param[2:]...)
}
输出结果为 1,3,4,5,5
解析,底层数组没变,append 操作把 切片前4位的值覆盖掉了