前言
Golang 数据结构之 Slice (一)
上一篇文章写了Slice的基本数据结构,而且提到了Slice可以自动扩容,这篇文章就简单看看Slice是怎么样扩容的。
append函数
说扩容之前,不得不说一下 Slice 内置的 append 函数,这是一个用于向slice 追加元素的函数
func main(){
var nums []int
for i := 0; i < 10; i++ {
nums = append(nums, i)
}
fmt.Printf("nums: %+v", nums)
}
append 函数对于理解Slice 底层是如何进行工作的,有重要的意义。这是 builtin/builtin.go 中关于append func的说明,append 函数将元素附加到 Slice 的末尾。
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
- 如果它有空间足够,则重新划分目标以容纳新元素,即将元素附加在末尾;
- 如果没有,将分配一个新的底层数组,将原来的元素复制过去,再附加;
- append 返回更新后的切片。 因此需要将 append 的结果用输入的 slice 接收,这个情况后面会说到。
另外一个appendInt函数描述了这个过程
func appendInt(x []int, y int) []int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x) {
z = x[:zlen]
} else {
zcap := zlen
if zcap < 2*len(x) {
zcap = 2 * len(x)
}
z = make([]int, zlen, zcap)
copy(z, x) // 复制
}
z[len(x)] = y
return z
}
内置的 copy 函数将一个slice复制到另一个相同类型的 slice;
copy函数的第一个参数是要复制的目标slice,第二个参数是源slice;
两个slice可以共享同一个底层数组,甚至有重叠也没有问题;
copy函数将返回成功复制的元素的个数。
扩容
通过在每次扩大数组时直接将长度翻倍从而避免了多次内存分配,也确保了添加单个元素操的平均时间是一个常数时间。也大大提高了内存的使用效率。
func main() { var nums []int for i := 0; i < 10; i++ { nums = append(nums, i) fmt.Printf("cap=%d\t%v\n", cap(nums), nums) } }
通常我们并不知道 append 调用是否导致了扩容,因此我们也不能确认新的slice和源slice是否引用的是相同的底层数组。
同样,我们不能确认在源的slice上的操作是否会影响到新的slice。所以上面说的,通常是将append 返回的结果直接赋值给输入的 slice变 量
nums = append(nums, i)
下一篇文章分析一下 growslice 函数的源码