关于go:设置切片容量有什么意义?

What is the point in setting a slice's capacity?

在Golang中,我们可以使用内置的make()函数创建具有给定初始长度和容量的切片。

考虑以下几行,切片的长度设置为1,其容量设置为3:

1
2
3
4
5
6
func main() {
    var slice = make([]int, 1, 3)
    slice[0] = 1
    slice = append(slice, 6, 0, 2, 4, 3, 1)
    fmt.Println(slice)
}

我很惊讶地看到这个程序打印了:

[1 6 0 2 4 3 1]

这让我想知道——如果append()可以简单地吹过它,那么最初定义切片容量的意义是什么?设置足够大的容量是否有性能提升?


切片实际上只是管理底层数组的一种奇特方法。它自动跟踪大小,并根据需要重新分配新空间。

当您附加到一个切片时,它的容量在每次超过当前容量时都会加倍。它必须复制所有元素才能做到这一点。如果您知道在开始之前它将有多大,那么您可以通过预先获取它来避免一些复制操作和内存分配。

当你用一个片来提供容量时,你设置初始容量,而不是任何限制。

有关Slices的一些有趣的内部细节,请参阅Slices上的此博客文章。


一个slice是一个简单array的奇妙抽象。你有各种各样的好特性,但在它的核心深处,有一个array。(出于某种原因,我以相反的顺序解释以下内容)。因此,如果/当您指定3capacity时,在内存中分配一个长度为3的数组,您可以在不需要重新分配内存的情况下将该数组分配到append。该属性在make命令中是可选的,但请注意,无论您是否选择指定,slice都将始终具有capacity。如果您指定了一个length(它也一直存在),那么slice可以索引到该长度。其余的capacity隐藏在幕后,因此在使用append时,它不必分配一个全新的阵列。

下面是一个更好地解释力学的例子。

埃多克斯1〔15〕

基础arrayint的零值(即0的零值)的3一起分配:

埃多克斯1〔20〕

但是,length被设置为1,因此切片本身只打印[0],如果尝试索引第二或第三个值,它将panic,因为slice的机制不允许这样做。如果你对它使用s = append(s, 1),你会发现它实际上是被创建来包含zero的值,直到length,你最终会得到[0,1]。此时,您可以在整个基础array填充之前再次执行append,另一个append将强制它分配一个新的,并以双倍的容量复制所有值。这实际上是一个相当昂贵的操作。

因此,对您的问题的简短回答是,可以使用预分配capacity来大幅提高代码的效率。尤其是如果slice要么最终会非常大,要么包含复杂的structs(或两者兼有),因为structzero值实际上是其fields中每一个的zero值。这并不是因为它可以避免分配这些值,因为它无论如何都必须分配这些值,而是因为每次需要调整基础数组的大小时,append都必须重新分配新array中充满这些零值的值。

短操场示例:https://play.golang.org/p/lgayvlw-jr