3

不得不说的Slice

 3 years ago
source link: https://zhuanlan.zhihu.com/p/31267313
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

不得不说的Slice

可观测性、Kubernetes、云原生、Go,欢迎私聊!

好久没有更新了,久等了!

最近工作中遇到了slice的坑,所以分享下。

Go的切片是一个高效而强大的处理数据序列的类型,相比来说我们使用数组的时候更少,因为数组一旦定义它的长度不能再改变,使用起来不够灵活。但任何事情都会有副作用,就像之前说过的Go的语法糖带来便利的同时也隐藏了一些坑。有时候是出乎意料的特性,有时候是性能的损失,稍不注意可能就会陷入其中,给你一种这语言有“严重”的缺陷的感觉,严重了甚至很可能让你放弃使用它,之前就看到早期国外有开发团队就出现过这种情况。

slice可以从数组中生成,也可以直接创建,在创建的时候可以指定slice的长度和容量,如果没有指定容量(make的第三个参数),那么容量就默认和长度相同。也可以不通过make创建,而是直接通过var的方式声明一个零值的slice,则它的长度和容量都为0。

var s1 []byte = buffer[10:20]
var s2 []int
var s3 = make([]int, 10)
var s4 = make([]int, 10, 20)

slice底层元素是由一个数组保存着,它的容量就是这个数组的长度,你可以可以通过cap获得。如果在使用slice的过程,元素的数量超过了容量,那么就需要创建新的数组。这是一个值得注意的地方,如果你初始的时候就可以确定元素的最大数量,那么最好设置slice的容量的值,这样避免数组的重新分配和数据拷贝,提高程序的性能,但这里如果使用不当你可能会陷入困境,下面我们详细说。

索引

slice和数组一样,同样可以通过索引访问某个位置的元素。但如果超出slice的索引最大值,就会导致panic。

panic: runtime error: index out of range

想要删除的时候,就可以使用索引

s2 := append(s1[:2], s1[3:]...)

append

通过append方法可以往slice增加元素,需要清楚的是append后的返回值是增加元素后的slice,和原始的slice是不同的,尽管它们底层的数组可能相同(如果容量足够,元素就继续增加底层数组中,如果容量不够,则结果slice就会创建新的数组)。

这里还有一个地方,就是你同样可以同时append多个元素:

slice1 = append(slice1, x1, x2)
slice1 = append(slice1, slice2...)

copy

copy的返回结果为 dst和src的长度的较小值。这也就是说,如果dst的长度大,则将src全部元素都复制到dst中。如果src的长度大,则将src的len(dst)个元素复制到dst中。

copy(s1,s2)

上面提到slice底层使用数组,而在使用过程这个数组可能在多个slice中共用,这就带来潜在的问题。

更改连锁

test1 := make([]int, 0)
for i := 0; i < 8; i++ {
     test1 = append(test1, i)
}
test2 := test1
test2[0] = 99
fmt.Println(test1, test2)//[99 1 2 3 4 5 6 7] [99 1 2 3 4 5 6 7]

当我们修改test2时,test1也会被修改。想要原来的不被修改,那么你就需要新建一个test2,这里技巧就是可以使用copy。

隐藏数据

test1 := make([]int, 0)
for i := 0; i < 8; i++ {
	test1 = append(test1, i)
}
test2 := test1[0:2]
test3 := test1[0:3]
test2 = append(test2, 99)
fmt.Println(test1, test2, test3)//[0 1 99 3 4 5 6 7] [0 1 99] [0 1 99]

这个例子test2和test3都使用相同的数组test1,而且它们的容量都是8,不同的是它们的长度分别是2和3。

当往test2增加增加一个元素的时候,它事实上将元素放在的数组的索引为2的位置。所以这会对原始数组和test3都有影响。

copy的覆盖

调用copy方法的时候也可能会产生覆盖原数组

test1 := make([]int, 0)
for i := 0; i < 8; i++ {
	test1 = append(test1, i)
}
test2 := test1[0:2]
test3 := test1[2:6]
copy(test2, test3)
fmt.Println(test1, test2, test3)// [2 3 2 3 4 5 6 7] [2 3] [2 3 4 5]

make使用的规则

Ok,说了这么多,我都懂了,使用make来分配,知道了长度那么就事先指定长度。然而一不小心还是会出现错误。(PS:最近在开发X系统时就不小心碰到了,导致程序崩溃!)

// 我要一个长度为8的slice
test1 := make([]int, 8)
for i := 0; i < 8; i++ {
	test1 = append(test1, i)
}

fmt.Println(test1)// [0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7]

发生了什么?为什么前面这么多0?

这是因为当你预先给定了长度,那么这个长度之内的所有值都会被写入默认的类型的值,相当于append了指定长度个数的默认值。所以,如果你要指定长度,在后面的使用过程中,就要通过索引赋值,而不是append了。这是使用make的一个规则。切记!!!

编辑于 2017-11-21

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK