Go语言切片(Slice)

本文详细解析了Go语言中的切片概念,其底层实现原理,如何通过切片引用数组片段,以及切片的动态扩容机制。特别关注了切片与数组的内存管理和扩容过程中的内存拷贝影响。
摘要由CSDN通过智能技术生成

什么是切片

Go语言切片是对数组的抽象。它具有动态数组的功能,可以追加元素使切片的容量增大。
但是,需要说明的是,切片的底层还是用数组实现的,且它不是数组或数组指针。它自身是一个结构体,通过内部指针和相关属性引用数组片段,达到动态数组的效果。
可是只是理解概念并不能满足我们对切片实现原理的好奇心,它真的没有使用像链表类似的数据结构来实现的吗?

切片实现的底层原理

在Go语言中传递数组就是对数组值得拷贝,对于元素类型长度较大或者元素个数较多得数组,直接将数组当作参数进行传递,意味着要把整个数组进行拷贝一次,有着不小的性能损耗。
解决这一问题的办法是直接把数组指针当作形参,只传递数组首元素的地址即可。
切片大概也是以这样的方法进行避免这一问题的,但对于前者的作用更进一步。
切片的结构体

type slice struct {
	array unsafe.Pointer
	len int
	cap int
}
array :只想下层数组的指针。
len:切片的长度,即内置函数len()返回的值
cap: 切片的最大容量,即内置函数cap()返回的值, cap>=len

定义切片的几种方式

一个抽象的底层原理往往可以从它的定义方式来表现出来。

将数组进行切片

定义一个数组
a := [10]int {11,12,13,14,15,16,17,18,19,20}
定义数组的切片
s := a[3:7]
此时,数组和切片的内存布局就是这样的:
在这里插入图片描述
我们可以看得出来,其实切片时引用了数组的某一片段。array指向的时是底层数组的第一个元素。

切片的切片

同时,我们也可以对切片进行切片
基于s定义一个新的切片s1
s1 := s[1:3]
请添加图片描述

定义切片时超过底层数组的容量

就是在定义s1时的low:high超过s的cap大小,Go会怎么处理呢?
此时,会自动为切片建立一个底层数组,内存布局如下:
请添加图片描述
需要注意的是,如果想对底层数组进行切片,low:high的值不能超过底层数组的范围

切片动态扩容原理

切片很奇妙的一个地方就是可以通过append()方法对切片进行动态扩容。
实际上它的原理很简单,就是append会根据切片的需要,在当前底层数组的容量无法满足的情况下,重新创建一个新的更大容量的数组,把旧的数组数据复制到新的数组中。

func main() {
	arr := [3]int{1, 2, 3}
	/*
		记录当前数组的地址
	*/
	s := arr[:]
	//因为我们知道切片和数组的地址是一样的,所以现在记录切片还是数组都是可以的
	//为什么要将数组转化为切片?
	//因为数组无法动态扩容,只有切片可以
	var p1 *[3]int
	p1 = &arr

	//进行append
	s = append(s, 1)
	//记录append后的数组地址
	var p2 *[]int
	p2 = &s
	fmt.Println(p1, p2)
	fmt.Println(&p1, &p2)
}

我们看一下输出结果
在这里插入图片描述
元素的确追加了,并且两个切片的地址是不同的。在append扩容之后,底层确实创建了一个新的切片进行拷贝。

总结

切片的存在确实让我们对数组有了更加灵活的操作。在扩容方面,拷贝后的旧数组会被gc垃圾回收,也避免了多余的内存占用,但是拷贝的过程所消耗的时间是无法避免的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值