golang中slice的剖析

摘要:

读完本文,能获得什么

1、能够详细了解切片的结构

2、能够了解切片和数组的关系和不同

3、能够了解切片的扩容规则和逻辑。

下面开始:

1、先初始化一个切片slice.

a := make([]int, 1, 2)

As we know, Slice has three params!

第一个参数是指定切片的类型,第二个参数是指定存储多少个数据 len。第三个参数是指定空间cap (分配了多少个数据的内存,或者叫分配的内存最多可以存储多少个数据)

当make完上面的代码后,系统会为这个a变量分配一个容纳2个int元素的连续内存,默认值是0,并且只有1个可以使用。因为make的第二个参数len的位置我们填写了1,所以只能使用a[0],如果直接使用a[1],就会越界直接报错。

使用append,给切片的len增加一个值,这样可以把第二个位置填上。这时候a的len就成2了,直接使用a[1]就没有问题了。

a = append(a, 2)

暂时总结:切片,其实就是一个由三部分组成的结构体。如下图所示,结构体包括指向的一个数组的地址、len一共存了多少元素,cap从指向的地址开始数一直到本内存结束一共能存多少个元素(这个有点绕,下面具体要说一下)。

2、通过一个数组的对比,看懂切片和数组的区别是什么

arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

这是一个常规的数组,不是切片!数组的数量是不能改变的,这就是数组。

arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

slice1 := arr[3:5]

fmt.Println(slice1)

// 输出的值是 [4 5]

如果在数组的基础上,定义一个切片slice1,这时候,slice1的len是2,cap是7,len是2没问题可以理解,为什么cap是7没法理解了。

看下图,1-10是数组的一个连续内存,切片[3:5],它并没有生成新的内存,而是指向了1-10这个内存的第3个位置作为切片的第0个数据,一共切了2个,但cap的定义是在一段连续的内存中,从指向的位置开始一直到结束能够存放多少个数量,所以是7个。

注意:如果给slice1切片增加一个数据,是不能修改人家原来的数组的,它会把值拷贝出来新建一个新的数组,len=3,cap=7。这样就不会破坏数组的原有的值。

arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

slice1 := arr[3:5]

slice1 = append(slice1, 11)

slice的背后也是封装了数组作为支持的,这样应该能看懂数组和切片的关系了。

3、当切片append到当前连续的内存满了会怎么样

如下面的代码,make初始化了一个切片slice1,len=2,cap也等于2,相当于初始化把内存占满了。这时候又append一个元素进来。这时候就会开始扩容,扩容的意思就是,老的内存将被放弃,重新申请一块cap为4的内存,这里扩容是翻倍扩容,切片重新指向新的内存地址,并且把数据迁移过去,同时append一个新的值进来,len变成了3.

slice1 := make([]int, 2, 2)

slice1 = append(slice1, 3)

4、切片的扩容规则

切片的扩容规则在源码中可以找到,感兴趣的可以读一读源码 runtime/slice.go文件。

 简单说说切片的扩容规则:源码中的cap是所需数量,oldcap是原来的数量,newcap是经过计算最终的数量。

从源码逻辑看:

如果所需数量大于老cap的两倍,那么直接返回所需数量为新cap数量。

否则:

        如果元素数量小于1024个就直接翻倍返回

        如果大于1024个了,就增加四分之一的cap空间。

猜你喜欢

转载自blog.csdn.net/weixin_38256311/article/details/122994307