Go语言中的slice,又称切片,比较类似动态数组,类似Java中的List,slice支持自动扩容,是常用的数据接口之一。
在了解slice之前,先介绍下,Go中的数组,数组是长度固定且类型相同的连续序列,有三个重要特征,(1)长度固定(2)元素类型相同(3)连续序列,长度相同和元素类型相同的2个数组可以称为这两个数组类型是一样的,连续序列指的是在内存中数组占用的是一段连续的内存。
Go中的数组是值类型的,所以在将数组传递进函数时,会发生值拷贝,即深拷贝一份数组给函数的形参,如果数据较大,会带来一定的性能问题,比较常见的做法是使用指针,在Go中更建议使用slice来代替数组的使用。
切片对于数组就像文件描述符对于文件,数组通常作为slice的底层存储存在,slice也可以看作是数组的窗口,使用slice屏蔽了底层数组的操作和扩容等,是非常常用的数据结构。
slice由三部分构成:(1)指向第一个元素的指针(2)长度(3)容量
这也就意味着slice是一个轻量级的,在传参时值拷贝也只是拷贝一个指针,slice的长度指的是开在数组上的窗口的大小,slice的容量指的是在底层数组上,从指针位置到数组末尾的大小。
下面的代码展示了切片的定义方式:(1)直接创建切片,runtime会在底层帮构建数组
(2)在已有的数组上切片化,切片上也可以切片化,此时会存在多个切片共享一个底层数组。
slice的自动扩容机制可以查看 $GOROOT/src/runtime/slice.go中的growslice函数
slice在自动扩容时会新建一个新的数组,然后将老数组的元素复制到新数组中,注意的是不会将老的slice指向这个新的数组,这和Java中的ArrayList的实现机制不同,所以在使用Go中的slice时,在append操作时,需要使用原有的slice变量接收下append的结果,否则发生自动扩容后,slice变量绑定的还是老的数组。
Go
package main
import "fmt"
func main() {
// 1 数组是固定长度 且 类型相同的连续序列
// 因此 数组类型有2个属性:元素类型 和 数组长度
// 只有 元素类型 和 长度 都相同的数组才能称之为同类型的数组
// go语言的数组是值类型的,所以在传递数组到函数内时,发生的是值拷贝,如果数组比较大会有性能问题
// 比较常见的做法是 将数组传递给函数的时候,传指针,但是在go中建议的做法是使用切片
// 2 切片对于数组 就像 文件描述符对于文件
// slice由三个结构构成,分别是 指针array 长度len 容量cap
// array: 指向底层数组某元素的指针,该元素是切片的起始元素
// len: 切片的长度,即切片中的元素个数
// cap: 切片的最大容量
// 3 直接创建切片,会在底层创建一个数组,数组的长度为5
s := make([]byte, 5)
fmt.Printf("s = %v\n", s)
// 4 通过数组的 切片化 构造切片
// 注意 当从一个数组上 切片化出多个切片时,切片对数组的修改是可能会影响其他切片的
u := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
us1 := u[3:7]
fmt.Printf("us1 = %v\n", us1)
// 5 切片的动态扩容
var s1 []int
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 0, cap = 0
s1 = append(s1, 11)
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 1, cap = 4
s1 = append(s1, 12)
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 2, cap = 4
s1 = append(s1, 13)
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 3, cap = 4
s1 = append(s1, 14)
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 4, cap = 4
s1 = append(s1, 15)
fmt.Printf("len = %v, cap = %v\n", len(s1), cap(s1)) // len = 5, cap = 8
}