Golang的数组与切片
- 相同点:都属于集合类的类型,而且他们的值也都可以用来存储某一种类型的值
- 不同点:数组类型的值的长度是固定的,而切片类型的值是可以变长的
切片是对数组的一层简单封装,在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的应用
- Go语言的切片属于引用类型,(引用类型还有通道内心,函数类型等)
- GO语言的数组属于值类型(值类型的还有几处数据类型以及结构体类型)
长度与容量
len函数获取数组和切片的长度(实际数量),cap函数获得容量(可容纳的数量)
s1 := make([]int, 5)
s2 := make([]int, 5, 8)
在声明s1的时候把它的长度设置成了5,s1的容量是5,s2的容量是8. 如果用make函数初始化切片时,如果不指明容量,那么他的容量和长度一致。
切片的容量实际上代表了它的底层数组的长度
s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)
fmt.Println()
s4=s3[3:6]
这里的3可被称为起始索引,6可被称为结束索引。那么s4的长度就是6减去3,即3。
因此可以说,s4中的索引从0到2指向的元素对应的是s3及其底层数组中索引从3到5的那 3 个元素。
一个切片的容量可以被看作是透过这个窗口最多可以看到的底层数组中元素的个数。
由于s4是通过在s3上施加切片操作得来的,所以s3的底层数组就是s4的底层数组
在底层数组不变的情况下,切片代表的窗口可以向右扩展,直至其底层数组的末端
所以s4的容量就是其底层数组的长度8,减去上述切片表达式的那个启始索引3,8-3=5;
注意,切片代表的窗口是无法向左扩展的。也就是说,我们永远无法透过s4看到s3中最左边的那三个元素。
怎样估算切片容量的增长?
一旦一个切片无法容纳更多的元素,Go 语言就会想办法扩容。但它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。
但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。
对应源码:runtime包中 slice.go 文件里的growslice及相关函数的具体实现。
切片的底层数组什么时候会被替换?
确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。
它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。
请记住,在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,函数返回的是指向新底层数组的新切片。所以,严格来讲,“扩容”这个词用在这里虽然形象但并不合适。
有疑问加站长微信联系(非本文作者)