前言
这两篇文章分别介绍了从make、array/slice切片构造slice的具体底层处理过程,本文则介绍通过append生成新的slice的过程。
Slice append
append func
// 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"...)
func append(slice []Type, elems ...Type) []Type
以上为builtin/builtin.go中关于append func的说明。append会返回一个新的slice,因此必须保存append的结果。
我们知道,append会追加一个或多个数据至slice中,这些数据会存储在slice的持有的数组中。
数组的长度是固定的,意味着存储的数据是有限的。剩余空间足以容纳追加的数据,则可以正常将数据存入数组。一旦追加数据后总长度超过数组长度后,则原数组已无法存储新数据。那要怎么处理呢?
runtime/slice.go中只有扩容的growslice func,其调用主要在cmd/compile/internal/gc/walk.go中,处理相对复杂。我们可以先看下refelct/value.go下的Append源码,此部分的处理过程很完整和简单。
reflect Append
// Append appends the values x to a slice s and returns the resulting slice.
// As in Go, each x's value must be assignable to the slice's element type.
func Append(s Value, x ...Value) Value {
s.mustBe(Slice)
s, i0, i1 := grow(s, len(x))
for i, j := i0, 0; i < i1; i, j = i+1, j+1 {
s.Index(i).Set(x[j])
}
return s
}
// grow grows the slice s so that it can hold extra more values, allocating
// more capacity if needed. It also returns the old and new slice lengths.
func grow(s Value, extra int) (Value, int, int) {
i0 := s.Len()
i1 := i0 + extra
if i1 < i0 {
panic("reflect.Append: slice overflow")
}
m := s.Cap()
if i1 <= m {
return s.Slice(0, i1), i0, i1
}
if m == 0 {
m = extra
} else {
for m < i1 {
if i0 < 1024 {
m += m
} else {
m += m / 4
}
}
}
t := MakeSlice(s.Type(), i1, m)
Copy(t, s)
return t, i0, i1
}
Append处理过程如下:
- 判断当前slice长度i0与追加数据的总长度i1是否溢出,溢出则报错;
- 若i1小于/等于slice的cap(底层数组的长度),直接返回原slice的起始及结束数据部分
- 否则,当前底层数组已无法存储所有的追加数据,需要进行扩容处理:
-
若当前cap为0,则直接已追加数据的长度为新cap;
-
若i1大于slice的cap m,开始逐步扩容cap,直至大于总数据总长i1
- 若原数据长度i0<1024,则m翻倍;
- 否则,m自增1/4
-
构建新的Slice
-
将原slice的数据拷贝至新slice中,并返回新slice。
- 将追加的数据存入指定的位置中
append
append的具体调用处理在cmd/compile/internal/gc/walk.go中,核心处理代码如下:
// Node ops.
const (
OXXX Op = iota
...
OAPPEND // append(List); after walk, Left may contain elem type descriptor
...
)
...
...
case OAPPEND:
// x = append(...)
r := n.Right
if r.Type.Elem().NotInHeap() {
yyerror("%v is go:notinheap; heap allocationdisallowed"