12.2 切片重组

通过改变切片长度得到新切片的过程称之为切片重组 reslicing。
slice1 := make([]type, start_length, capacity)

做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)。

当我们在一个切片基础上重新划分一个切片时,新的切片会继续引用原有切片的数组。如果你忘了这个行为的话,在你的应用分配大量临时的切片用于创建新的切片来引用原有数据的一小部分时,会导致难以预期的内存使用。

package main
import "fmt"
func get() []byte {  
    raw := make([]byte, 10000)
    fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址
    return raw[:3]  // 10000个字节实际只需要引用3个,其他空间浪费
}
func main() {  
    data := get()
    fmt.Println(len(data), cap(data), &data[0]) // 显示: 3 10000 数组首字节地址
}

为了避免这个陷阱,我们需要从临时的切片中使用内置函数 copy(),拷贝数据(而不是重新划分切片)到新切片。

package main
import "fmt"
func get() []byte {
	raw := make([]byte, 10000)
	fmt.Println(len(raw), cap(raw), &raw[0]) // 显示: 10000 10000 数组首字节地址
	res := make([]byte, 3)
	copy(res, raw[:3]) // 利用copy 函数复制,raw 可被GC释放
	return res
}
func main() {
	data := get()
	fmt.Println(len(data), cap(data), &data[0]) // 显示: 3 3 数组首字节地址
}
程序输出:
10000 10000 0xc000086000
3 3 0xc000050098

append() 内置函数:

func append(s S, x ...T) S  // T是S元素类型

append() 函数将 0 个或多个具有相同类型S的元素追加到切片s后面并且返回新的切片;追加的元素必须和原切片的元素同类型。如果s的容量不足以存储新增元素,append() 会分配新的切片来保证已有切片元素和新增元素的存储。

因此,append() 函数返回的切片可能已经指向一个不同的相关数组了。append() 函数总是返回成功,除非系统内存耗尽了。

s0 := []int{0, 0}
s1 := append(s0, 2)                // append 单个元素     s1 == []int{0, 0, 2}
s2 := append(s1, 3, 5, 7)          // append 多个元素    s2 == []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...)            // append 一个切片     s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...)   // append 切片片段    s4 == []int{3, 5, 7, 2, 3, 5, 7, 0, 0}

append() 函数操作如果导致分配新的切片来保证已有切片元素和新增元素的存储,也就是返回的切片可能已经指向一个不同的相关数组了,那么新的切片已经和原来切片没有任何关系,即使修改了数据也不会同步。

append() 函数操作后,有没有生成新的切片需要看原有切片的容量是否足够。

下一节:多个切片可以引用同一个底层数组。在某些情况下,在一个切片中添加新的数据,在原有数组无法保持更多新的数据时,将导致分配一个新的数组。而现在其他的切片还指向老的数组(和老的数据)。