社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Go语言中的切片是常用的一种数据类型,其中切片的底层是数组,切片常用的属性有长度和容量。
其中长度很容易理解,但是容量相对复杂一些。
切片提供了计算容量的函数 cap()
可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度。
以下有几个实例,第一:
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
testSlice := slice[1:2]
fmt.Println("cap slice:", cap(slice))
fmt.Println("cap newSlice:", cap(newSlice))
fmt.Println("cap test:", cap(testSlice))
分别打印什么呢?
答案是:
cap slice: 5
cap newSlice: 4
cap test: 4
首先,我们看slice的声明方法,直接声明了一个元素为int的切片,并且赋值,可以理解为底层是一个5个元素的数组,所以slice的容量为5,那么从slice上截取得到的newSlice和testSlice长度不同,为何容量是一样的呢?我们看下边这张图:
其中切片y是x从下标1开始,长度为2,容量为4,指针指向的位置为切片的起始位置,所以不难理解,为什么前一个例子总newSlice与testSlice的容量为何相同了,是因为底层数组的长度是一样的。可以简单记忆为:切片的容量是只与切片的起始下标有关,是底层数组减去起始位置。
再看第二个例子:
图片源自文章: https://www.ardanlabs.com/blog/2013/12/three-index-slices-in-go-12.html
我们常见的切片中只包含一个冒号,用来标识起始位置与结束位置(左闭右开),起始从Go1.2起,支持第三个参数,用来指定该切片的容量,如上图所示,有切片slice[i:j:k],其中切片的长度为j-i,容量为k-i。
接下来是第三个实例:
来源于Stack Overflow https://stackoverflow.com/questions/38573983/capacity-of-slices-in-go
package main
import "fmt"
func main() {
var a []int
printSlice("a", a)
// append works on nil slices.
a = append(a, 0)
printSlice("a", a)
// the slice grows as needed.
a = append(a, 1)
printSlice("a", a)
// we can add more than one element at a time.
a = append(a, 2, 3, 4)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %vn",
s, len(x), cap(x), x)
}
会输出什么呢?
答案是:
a len=0 cap=0 []
a len=1 cap=1 [0]
a len=2 cap=2 [0 1]
a len=5 cap=6 [0 1 2 3 4]
这里边包含了切片长度扩展的原则:
当切片容量难以满足切片长度时,需要进行扩容,由于增加切片容量需要消耗性能,所以Go默认的是会将切片的容量提高一倍,类似于网络中窗口大小每次也是乘以二。
那么上图中为什么第三次append执行了之后,容量变成了6呢?我认为是由于同时添加了多个元素,所以会根据之前的步长也就是2反复的扩容,直到容量够用,为了验证这个猜想,我们将第三次append改为2,3,4,5,6,7,8,得到的结果是
a len=9 cap=10 [0 1 2 3 4 5 6 7 8]
但是如果我们把多个元素拆开进行append就会验证上边的默认规则,代码如下:
package main
import "fmt"
func main() {
var a []int
printSlice("a", a)
// append works on nil slices.
a = append(a, 0)
printSlice("a", a)
// the slice grows as needed.
a = append(a, 1)
printSlice("a", a)
a = append(a, 2)
printSlice("a", a)
a = append(a, 3)
printSlice("a", a)
a = append(a, 4)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %vn",
s, len(x), cap(x), x)
}
打印结果如下:
a len=0 cap=0 []
a len=1 cap=1 [0]
a len=2 cap=2 [0 1]
a len=3 cap=4 [0 1 2]
a len=4 cap=4 [0 1 2 3]
a len=5 cap=8 [0 1 2 3 4]
可以看到,切片的容量是0->1->2->4->8,当切片容量不足时,继续添加元素容量会扩大一倍。
PS: 在Stack Overflow链接中,这里其实是有异议的,在不同的架构/Go版本中,扩容的策略可能会有不同,详情请参考链接中的回复。这里不做过多说明,有不同意见欢迎在评论中讨论。
最后一个实例,其实与之前一篇关于切片是否是传引用调用的博文有关,有兴趣的同学可以翻看一下
https://blog.csdn.net/yuanlaidewo000/article/details/81133350
先看代码:
package main
import (
"fmt"
)
func main() {
slice := []int{10, 20, 30, 40, 50}
newSlice := slice[1:3]
// 使用原有切片划分出新切片,容量为4
// 将新元素赋值为 60,会改变底层数组中的元素
newSlice = append(newSlice, 60) //这里会影响原切片的值,40->60
fmt.Println(newSlice)
fmt.Println(slice)
slice1 := []int{10, 20, 30, 40, 50}
newSlice1 := slice1[1:3:3] // 新切片容量为2
newSlice1 = append(newSlice1, 60) //这里就不会影响
fmt.Println(newSlice1)
fmt.Println(slice1)
}
打印的结果如下:
[20 30 60]
[10 20 30 60 50]
[20 30 60]
[10 20 30 40 50]
为什么第一次会影响原切片,而第二次不会呢?
其实是由于第一次append元素并没有发生扩容,所以其实会修改下标为3的元素,即将40改为了60;但第二次在slice1中,由于切片的容量指定为2,所以再次append的时候发生了扩容,Go会将原来的值拷贝一份并指向新的底层数组,所以append并不会影响原来的切片(底层并不是同一个数组了)。
以上都是切片中的一些细节性问题,如有问题还请各位指正
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!