golang数据结构(一):slice

slice实现原理

slice依托数组实现,底层数组对用户屏蔽,底层数组容量不足的时候会触发扩容,具体扩容的倍数依托实际场景。

slice数据结构

type slice struct {
    array   unsafe.Pointer  //指向底层数组
    len     int             //表示切片长度
    cap     int             //表示数组容量
}

创建slice的方式

用make创建slice

使用make创建的时候可以指定slice的长度和容量,同时底层会分配一个数组,数组长度为结构体中的cap,即容量。

例如:

slice := make([]int, 5, 10)语句创建的slice结构如图:

在这个slice里,可以使用下标slice[0] ~ slice[4]来操作里面的元素,cap为10,表示后续向slice添加新的元素时可以不必重新分配内存,可以直接使用预留的内存,创建的时候预留内存,这样在添加元素的时候可以避免发生slice的扩容,但是如果预留的空间过大则会产生浪费。

用数组创建slice

如果使用数组创建slice的话,slice和原数组共用一部分内存

例如:

slice := array[4:6]语句创建的slice结构如图:

切片从数组array[4]开始,到数组array[6]结束(不含array[6]),即创建的切片在array中的范围是左闭右开区间[4,6),所以切片长度为2,数组后面的内容都作为切片的预留内存,即cap为4。

数组和切片操作可能作用于同一块内存,这也是使用过程中需要注意的地方。

slice扩容

扩容触发时机

在使用append函数向slice添加元素时,如果slice可使用的空间不足,即len>=cap的时候,会触发slice的扩容。

扩容的过程

slice的扩容会重新分配一块更大的内存,将原slice中的数据拷贝到新的slice中,然后返回新的slice,再把元素添加进去。

例如,在向一个cap和len都为6的slice再添加一个元素的时候,会触发扩容,如图:

扩容操作只关心容量,而不包括追加数据的添加,即扩容只负责把原Slice数据拷贝到新Slice,追加的数据有append函数在扩容结束后的新slice中添加。如上图所示,扩容后新的slice长度没变,仍然为6,但是容量已经变为12了,原slice中的数据也被拷贝到了新的slice所指向的数组中。

扩容原则

扩容容量的选择有两种,具体遵循以下规则:

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍

  • 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍

分析完扩容机制,那么可以看一看append函数向slice添加新元素的具体步骤:

  1. 如果slice还有容量,会把新的元素添加进去,然后slice.len++,返回原slice

  2. 如果slice容量不足,先扩容,然后得到新的slice

  3. 将新的元素添加进新的slice中,然后slice.len++,返回新slice

slice拷贝

使用copy函数拷贝两个切片时,会将源切片中的数据逐一拷贝到目标切片所指向的数组中,拷贝的数量是两个切片长度的最小值,也就是说假如一个长度为5的切片拷贝到长度为2的切片时,只会拷贝2个元素,所以copy的过程中不会发生扩容。

特殊的切片

根据刚才所说的,使用数组或切片生成新的切片一般使用slice := array[start:end]的方式,这种新生成的切片并没有指定切片的容量,实际上新切片的容量是从start开始直至array的结束。

比如下面两个切片,长度和容量都是一致的,使用共同的内存地址:

sliceA := make([]int, 5, 10) 
sliceB := sliceA[0:5]

另一种写法是创建新切片的同时也指定容量,即slice[start:end:cap], 其中cap即为新切片的容量,当然容量不能超过原切片实际值,如下所示:

sliceA := make([]int, 5, 10)  //length = 5; capacity = 10 
sliceB := sliceA[0:5]         //length = 5; capacity = 10 
sliceC := sliceA[0:5:5]       //length = 5; capacity = 5

使用slice的注意事项

  • 创建切片的时候可以根据实际情况预分配容量,这样可以在后续使用时避免添加元素的时候频繁地触发扩容,有助于提升性能;

  • 尽量不要使用多个切片操作同一个数组,防止读写数据的冲突;

  • 切片拷贝需要考虑实际拷贝元素的个数;

总结

  • 切片的底层是数组,每一个切片都指向一个底层数组

  • 切片实际上是一个结构体,包括三个关键字,array是当前切片所指向的数组,len是切片长度,cap是切片容量

  • 使用len()计算切片长度时间复杂度为O(1),不需要遍历切片

  • 使用cap()计算切片容量时间复杂度为O(1),不需要遍历切片

  • 通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体

  • 使用append()向切片追加元素时有可能触发扩容,扩容后将会生成新的切片

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萝卜说菜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值