go语言切片扩容出现问题 go 切片append

go语言中实现切片(slice)的三种方式定义一个切片,然后让切片去引用一个已经创建好的数组 。基本语法如下:
索引1:切片引用的起始元素位
索引2:切片只引用该元素位之前的元素
例程如下:
在该方法中,我们未指定容量cap,这里的值为5是系统定义的 。
在方法一中,可以用arr数组名来操控数组中的元素 , 也可以通过slice切片来操控数组中的元素 。切片是直接引用数组,数组是事先存在的,程序员是可见的 。
通过 make 来创建切片,基本语法如下:
make函数第三个参数cap即容量是可选的,如果一定要自己注明的话,要注意保证cap≥len 。
用该方法可以 指定切片的大小(len)和容量(cap)
例程如下:
由于未赋值系统默认将元素值置为0,即:
数值类型数组:默认值为 0
字符串数组:默认值为 ""
bool数组:默认值为 false
在方法二中,通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素 。
定义一个切片,直接就指定具体数组,使用原理类似于make的方式 。
例程如下:
GoLang中的切片扩容机制[5]int是数组go语言切片扩容出现问题,而[]int是切片 。二者看起来相似,实则是根本上不同go语言切片扩容出现问题的数据结构 。
切片的数据结构中 , 包含一个指向数组的指针array,当前长度len,以及最大容量cap。在使用make([]int, len)创建切片时,实际上还有第三个可选参数cap,也即make([]int, len, cap)。在不声明cap的情况下 , 默认cap=len。当切片长度没有超过容量时,对切片新增数据,不会改变array指针的值 。
当对切片进行append操作 , 导致长度超出容量时,就会创建新的数组,这会导致和原有切片的分离 。在下例中
由于a的长度超出go语言切片扩容出现问题了容量,所以切片a指向了一个增长后的新数组,而b仍然指向原来的老数组 。所以之后对a进行的操作,对b不会产生影响 。
试比较
本例中,a的容量为6,因此在append后并未超出容量,所以array指针没有改变 。因此,对a进行的操作,对b同样产生了影响 。
下面看看用a := []int{}这种方式来创建切片会是什么情况 。
可以看到,空切片的容量为0,但后面向切片中添加元素时 , 并不是每次切片的容量都发生了变化 。这是因为 , 如果增大容量 , 也即需要创建新数组,这时还需要将原数组中的所有元素复制到新数组中,开销很大 , 所以GoLang设计了一套扩容机制 , 以减少需要创建新数组的次数 。但这导致无法很直接地判断append时是否创建了新数组 。
如果一次添加多个元素,容量又会怎样变化呢?试比较下面两个例子:
那么,是不是说,当向一个空切片中插入2n-1个元素时,容量就会被设置为2n呢?go语言切片扩容出现问题我们来试试其他的数据类型 。
可以看到,根据切片对应数据类型的不同,容量增长的方式也有很大的区别 。相关的源码包括: src/runtime/msize.go,src/runtime/mksizeclasses.go 等 。
go语言切片扩容出现问题我们再看看切片初始非空的情形 。
【go语言切片扩容出现问题 go 切片append】 可以看到,与刚刚向空切片添加5个int的情况一致,向有3个int的切片中添加2个int,容量增长为6 。
需要注意的是 ,  append对切片扩容时 , 如果容量超过了一定范围,处理策略又会有所不同 。可以看看下面这个例子 。
具体为什么会是这样的变化过程,还需要从 源码 中寻找答案 。下面是src/runtime/slice.go中的growslice函数中的核心部分 。

推荐阅读