go语言切片扩容技巧 go 切片添加切片( 二 )


所以,切片属于引用类型 。
通过这种方式可以将数组转换为切片 。
中间不加三个点就是切片,使用这种方式创建切片,实际上是先创建数组,然后再通过第一种方式创建 。
使用make创建切片,就不光编译期了 , make创建切片会涉及到运行期 。1. 切片的大小和容量是否足够?。?
切片是否发生了逃逸,最终在堆上初始化 。如果切片小的话会先在栈或静态区进行创建 。
切片有一个数组的指针,len是指切片的长度, cap指的是切片的容量 。
cap是在初始化切片是生成的容量 。
发现切片的结构体是数组的地址指针array unsafe.Pointer,而Go中数组的地址代表数组结构体的地址 。
slice 中得到一块内存地址 , array[0]或者unsafe.Pointer(array[0]) 。
也可以通过地址构造切片
nil切片:指的unsafe.Pointer 为nil
空切片:
创建的指针不为空,len和cap为空
当一个切片的容量满了,就需要扩容了 。怎么扩,策略是什么?
如果原来数组切片的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区域 , 把原来的值拷贝过来 , 然后再执行 append() 操作 。这种情况对现数组的地址和原数组地址不相同 。
从上面结果我们可以看到 , 如果用 range 的方式去遍历一个切片 , 拿到的 Value 其实是切片里面的值拷贝,即浅拷贝 。所以每次打印 Value 的地址都不变 。
由于 Value 是值拷贝的,并非引用传递,所以直接改 Value 是达不到更改原切片值的目的的,需要通过 slice[index] 获取真实的地址 。
GoLang中的切片扩容机制[5]int是数组,而[]int是切片 。二者看起来相似,实则是根本上不同的数据结构 。
切片的数据结构中,包含一个指向数组的指针array,当前长度len,以及最大容量cap。在使用make([]int, len)创建切片时,实际上还有第三个可选参数cap,也即make([]int, len, cap)。在不声明cap的情况下,默认cap=len。当切片长度没有超过容量时,对切片新增数据,不会改变array指针的值 。
当对切片进行append操作,导致长度超出容量时,就会创建新的数组 , 这会导致和原有切片的分离 。在下例中
由于a的长度超出了容量,所以切片a指向了一个增长后的新数组,而b仍然指向原来的老数组 。所以之后对a进行的操作,对b不会产生影响 。
试比较
本例中,a的容量为6,因此在append后并未超出容量 , 所以array指针没有改变 。因此,对a进行的操作,对b同样产生了影响 。
下面看看用a := []int{}这种方式来创建切片会是什么情况 。
可以看到,空切片的容量为0,但后面向切片中添加元素时,并不是每次切片的容量都发生了变化 。这是因为 , 如果增大容量,也即需要创建新数组 , 这时还需要将原数组中的所有元素复制到新数组中 , 开销很大,所以GoLang设计了一套扩容机制 , 以减少需要创建新数组的次数 。但这导致无法很直接地判断append时是否创建了新数组 。
如果一次添加多个元素,容量又会怎样变化呢?试比较下面两个例子:
那么,是不是说,当向一个空切片中插入2n-1个元素时,容量就会被设置为2n呢?我们来试试其他的数据类型 。
可以看到,根据切片对应数据类型的不同,容量增长的方式也有很大的区别 。相关的源码包括: src/runtime/msize.go  ,  src/runtime/mksizeclasses.go 等 。
我们再看看切片初始非空的情形 。
【go语言切片扩容技巧 go 切片添加切片】

推荐阅读