Slices in Go: why does it allow appending more tha

2019-08-08 12:15发布

The capacity parameter in making a slice in Go does not make much sense to me. For example,

aSlice := make([]int, 2, 2) //a new slice with length and cap both set to 2
aSlice = append(aSlice, 1, 2, 3, 4, 5) //append integers 1 through 5
fmt.Println("aSlice is: ", aSlice)  //output [0, 0, 1, 2, 3, 4, 5]

If the slice allows inserting more elements than the capacity allows, why do we need to set it in the make() function?

2条回答
老娘就宠你
2楼-- · 2019-08-08 12:37

The builtin append() function uses the specified slice to append elements to if it has a big enough capacity to accomodate the specified elements.

But if the passed slice is not big enough, it allocates a new, big enough slice, copies the elements from the passed slice to the new slice and append the elements to that new slice. And returns this new slice. Quoting from the append() documentation:

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:

When making a slice with make if the length and capacity are the same, the capacity can be omitted, in which case it is defaulted to the specified length:

// These 2 declarations are equivalent:
s := make([]int, 2, 2)
s := make([]int, 2)

Also note that append() appends elements after the last element of the slice. And the above slices already have len(s) == 2 right after declaration so if you append even just 1 element to it, it will cause a reallocation as seen in this example:

s := make([]int, 2, 2)
fmt.Println(s, len(s), cap(s))
s = append(s, 1)
fmt.Println(s, len(s), cap(s))

Output:

[0 0] 2 2
[0 0 1] 3 4

So in your example what you should do is something like this:

s := make([]int, 0, 10) // Create a slice with length=0 and capacity=10
fmt.Println(s, len(s), cap(s))
s = append(s, 1)
fmt.Println(s, len(s), cap(s))

Output:

[] 0 10
[1] 1 10

I recommend the following blog articles if you want to understand slices in more details:

Go Slices: usage and internals

Arrays, slices (and strings): The mechanics of 'append'

查看更多
Melony?
3楼-- · 2019-08-08 12:42

It is mainly an optimization, and it is not unique to go, similar structures in other languages have this as well.

When you append more than the capacity, the runtime needs to allocate more memory for the new elements. This is costly and can also cause memory fragmentation.

By specifying the capacity, the runtime allocates what is needed in advance, and avoids reallocations. However if you do not know the estimated capacity in advance or it changes, you do not have to set it, and the runtime reallocates what is needed and grows the capacity itself.

查看更多
登录 后发表回答