Go的数组和切片

相同和差异

数组和切片都是集合类型,都是可以用来存储某一类数据结构
但是数组的长度是固定的,切片的长度是可变的。
切片属于引用数据类型, 数组属于值类型。

关系探讨

切片可以看作是对数组的又一次封装,在切片的底层数据结构还是数组,切片就可以看作是是对数组内某个连续片段的引用。
lencap函数对数组和切片的结果也不同。
在数组中, 数组的长度len等于数组的容量cap
切片的长度则表示对底层数组的引用的连续片段的长度,容量则是底层数组的容量。
看一下通过通过make声明数组的结果

func main() {
	slice1 := make([]int, 4)
	slice2 := make([]int, 4, 8)
	fmt.Println("array len is", len(slice1))
	fmt.Println("array cap is", cap(slice1))
	fmt.Println("slice len is", len(slice2))
	fmt.Println("slice cap is", cap(slice2))
}

以上案例输出为:

slice1 len is 4
slice1 cap is 4
slice2 len is 4
slice2 cap is 8

结果说明如果在声明切片的时候不指定容量, 那么切片的容量就等于切片的长度。
同时使用make切片的引用是从底层数组的index为0元素开始的。 现在slice2 和底层数组的关系如下图

在这里插入图片描述
那么我们append一下slice2 , 代码

func main() {
	slice1 := make([]int, 4)
	slice2 := make([]int, 4, 8)
	fmt.Println("slice1 len is", len(slice1))
	fmt.Println("slice1 cap is", cap(slice1))
	fmt.Println("slice2 len is", len(slice2))
	fmt.Println("slice2 cap is", cap(slice2))
	slice3 := append(slice2, 1)
	fmt.Println("slice3 len is", len(slice3))
	fmt.Println("slice3 cap is", cap(slice3))
}

结果:

slice1 len is 4
slice1 cap is 4
slice2 len is 4
slice2 cap is 8
slice3 len is 5
slice3 cap is 8

可以看到slice3 的len 是5,但是cap依然是8。 说明底层数组并没有发生变化。
如果append导致len超过8呢

func main() {
	slice1 := make([]int, 4)
	slice2 := make([]int, 4, 8)
	fmt.Println("slice1 len is", len(slice1))
	fmt.Println("slice1 cap is", cap(slice1))
	fmt.Println("slice2 len is", len(slice2))
	fmt.Println("slice2 cap is", cap(slice2))
	for i := 0; i < 5; i++ {
		slice2 = append(slice2, 1)
	}
	fmt.Println("slice2 append len is", len(slice2))
	fmt.Println("slice2 append cap is", cap(slice2))
}

结果:

slice1 len is 4
slice1 cap is 4
slice2 len is 4
slice2 cap is 8
slice2 append len is 9
slice2 append cap is 16

发现底层数组的容量翻倍了,由于数组的容量和长度不可变,是切片又申请了一个新的底层数组来使用。

切片的切割

如果在原有的切片上截取新的切片, 那么新的切片使用的还是原来切片的数组吗?
看如下代码:

func main() {
	sliceAll := []int{1, 2, 3, 4, 5, 6, 7, 8}
	slice := sliceAll[2:6]
	fmt.Println("slice len is", len(slice))
	fmt.Println("slice cap is", cap(slice))
	sliceAll[2] = 88
	fmt.Println("slice cap is", slice[0])
}

结果:

slice len is 4
slice cap is 6
slice cap is 88

新的切片的容量不是8, 同时我们改变了原有切片的一个数字, 新的切片也改变了,说明两个切片依然是引用的同一个底层数组。

因为切片是无法向左拓展的,左边的数据没用,所以新的切片引用不是从index为0开始的,是从index为2开始的,如下图红框内部所示, 那么这个时候容量就是6了。说明切片的容量并不是一直等于底层数组的容量,而是引用起始的index到数组末尾的容量。
在这里插入图片描述
如果这个时候切片元素满了会怎么办,是基于切片的容量还是底层数组的容量来扩容呢?代码如下:

func main() {
	sliceAll := []int{1, 2, 3, 4, 5, 6, 7, 8}
	slice := sliceAll[2:6]
	fmt.Println("slice len is", len(slice))
	fmt.Println("slice cap is", cap(slice))
	sliceAll[2] = 88
	fmt.Println("slice cap is", slice[0])
	for i := 0; i < 3; i++ {
		slice = append(slice, 99)
	}
	fmt.Println("slice cap is", cap(slice))
	fmt.Println("sliceAll cap is", cap(sliceAll))
}

结果:

slice len is 4
slice cap is 6
slice cap is 88
slice cap is 12
sliceAll cap is 8

发现是基于6翻倍的,说明切面扩容不是基于底层数组的容量, 而是基于切片的容量,还有在申请新的数组的同时并没有改变原来切片sliceAll 的引用的数组,原有切片的容量还是8。

总结

切片是基于数组的一次封装, 解决了数组长度不可变的问题,但是其底层存储的实现依然是基于数组操作,尤其是容量不足的时候,会再次申请新的数组作为存储使用。 所以再切片的使用过程中如果可以提前确定容量,最好是声明容量创建,因为每次扩容,新的空间的申请和数据复制时比较消耗性能的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值