Go语言中的切片(Slices):为什么它允许追加超过容量允许的元素?

huangapple go评论76阅读模式
英文:

Slices in Go: why does it allow appending more than the capacity allows?

问题

在Go语言中,slicecapacity参数对我来说并没有太多意义。例如,

aSlice := make([]int, 2, 2) //创建一个长度和容量都为2的新切片
aSlice = append(aSlice, 1, 2, 3, 4, 5) //添加整数1到5
fmt.Println("aSlice is: ", aSlice)  //输出 [0, 0, 1, 2, 3, 4, 5]

如果切片允许插入的元素数量超过容量限制,那么为什么我们需要在make()函数中设置它呢?

英文:

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?

答案1

得分: 11

内置的append()函数使用指定的切片来追加元素,如果它有足够的容量来容纳指定的元素。

但是,如果传递的切片不够大,它会分配一个新的足够大的切片,将元素从传递的切片复制到新的切片,并将元素追加到新的切片中。并返回这个新的切片。引用自append()文档:

append内置函数将元素追加到切片的末尾。如果切片有足够的容量,目标切片将被重新切片以容纳新的元素。如果没有足够的容量,将分配一个新的底层数组。append返回更新后的切片。因此,有必要将append的结果存储在变量中,通常是存储切片本身的变量中:

使用make创建切片时,如果长度和容量相同,可以省略容量,此时容量默认为指定的长度:

// 这两个声明是等价的:
s := make([]int, 2, 2)
s := make([]int, 2)

还要注意,append()在切片的最后一个元素之后追加元素。上述切片在声明后立即具有len(s) == 2,因此如果您仅追加1个元素,它将导致重新分配,如下面的示例所示:

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

输出:

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

因此,在您的示例中,您应该这样做:

s := make([]int, 0, 10) // 创建一个长度为0、容量为10的切片
fmt.Println(s, len(s), cap(s))
s = append(s, 1)
fmt.Println(s, len(s), cap(s))

输出:

[] 0 10
1 1 10

如果您想更详细地了解切片,请参考以下博文:

Go Slices: usage and internals

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

英文:

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'

答案2

得分: 3

这主要是一种优化方法,不仅仅是Go语言独有的,其他语言中也有类似的结构。

当你追加的元素数量超过容量时,运行时需要为新元素分配更多的内存。这是昂贵的,并且可能导致内存碎片化。

通过指定容量,运行时提前分配所需的内存,并避免重新分配。然而,如果你事先不知道估计的容量或者容量会发生变化,你不需要设置它,运行时会根据需要重新分配内存并增加容量。

英文:

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.

huangapple
  • 本文由 发表于 2015年1月28日 01:11:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/28176059.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定