Golang slices.Grow
最后修改于 2025 年 4 月 20 日
本教程将介绍如何在 Go 中使用 slices.Grow 函数。我们将通过实际示例讲解切片容量管理。
slices.Grow 函数可以增加切片的容量,以保证有足够的空间容纳额外的元素。它是 Go 实验性 slices 包的一部分。
当您需要高效地追加大量元素时,此函数非常有用。它通过提前预分配容量来最大限度地减少内存分配。
基本的 slices.Grow 示例
slices.Grow 最简单的用法是为未来的追加操作准备切片。我们指定了预期要添加的额外元素的数量。
package main
import (
"fmt"
"slices"
)
func main() {
s := []int{1, 2, 3}
fmt.Println("Before Grow - len:", len(s), "cap:", cap(s))
s = slices.Grow(s, 5)
fmt.Println("After Grow - len:", len(s), "cap:", cap(s))
// Now we can append without reallocations
s = append(s, 4, 5, 6, 7, 8)
fmt.Println("After append - len:", len(s), "cap:", cap(s))
}
我们将切片增长 5 个元素,然后追加 5 个值。容量会增加以适应未来的添加,而不会发生多次分配。
增长一个空切片
slices.Grow 可用于空切片。此示例演示了如何为新切片预先分配容量。
package main
import (
"fmt"
"slices"
)
func main() {
var s []string
fmt.Println("Before Grow - len:", len(s), "cap:", cap(s))
s = slices.Grow(s, 10)
fmt.Println("After Grow - len:", len(s), "cap:", cap(s))
// Efficient appending
for i := 0; i < 10; i++ {
s = append(s, fmt.Sprintf("item%d", i))
}
fmt.Println("Final slice:", s)
}
空切片获得了容纳 10 个元素的容量。后续的追加操作不会触发重新分配,直到我们超出增长后的容量。
在已有容量的情况下增长
如果切片已具有足够的容量,slices.Grow 将不执行任何操作。此示例演示了此行为。
package main
import (
"fmt"
"slices"
)
func main() {
s := make([]int, 0, 10) // Initial capacity 10
fmt.Println("Before Grow - len:", len(s), "cap:", cap(s))
s = slices.Grow(s, 5) // Requesting 5 more
fmt.Println("After Grow - len:", len(s), "cap:", cap(s))
// Capacity remains the same
fmt.Println("No change if capacity sufficient")
}
由于切片已经有容纳 10 个元素的容量,因此请求额外的 5 个元素不会改变任何内容。函数会返回原始切片。
增长超出当前容量
当增长超出当前容量时,切片会获得一个新的底层数组。此示例展示了内存分配行为。
package main
import (
"fmt"
"slices"
)
func main() {
s := []int{1, 2, 3}
fmt.Printf("Original array address: %p\n", &s[0])
s = slices.Grow(s, 100) // Large growth
fmt.Printf("New array address: %p\n", &s[0])
fmt.Println("Capacity increased from 3 to:", cap(s))
}
内存地址会发生变化,因为 Go 会分配一个新的数组。容量会增长,以同时容纳现有元素和请求的增长。
性能比较
此示例比较了增长与重复追加的性能。它展示了预先分配的性能优势。
package main
import (
"fmt"
"slices"
"time"
)
func main() {
const size = 1_000_000
// Without pre-allocation
start := time.Now()
var s1 []int
for i := 0; i < size; i++ {
s1 = append(s1, i)
}
fmt.Println("Append without Grow:", time.Since(start))
// With pre-allocation
start = time.Now()
s2 := make([]int, 0)
s2 = slices.Grow(s2, size)
for i := 0; i < size; i++ {
s2 = append(s2, i)
}
fmt.Println("Append with Grow:", time.Since(start))
}
使用 slices.Grow 进行预先分配速度更快,因为它避免了多次重新分配。当切片很大时,这种差异会变得非常显著。
增长结构体切片
slices.Grow 可用于任何类型的切片。此示例演示了如何增长自定义结构体切片。
package main
import (
"fmt"
"slices"
)
type Point struct {
X, Y float64
}
func main() {
points := []Point{{1, 2}, {3, 4}}
fmt.Println("Initial capacity:", cap(points))
points = slices.Grow(points, 100)
fmt.Println("Grown capacity:", cap(points))
// Efficiently add many points
for i := 0; i < 100; i++ {
points = append(points, Point{float64(i), float64(i * 2)})
}
fmt.Println("Final length:", len(points))
}
我们将 Point 结构体切片增长以容纳 100 个额外的元素。与内置类型一样,相同的性能优势也适用于自定义类型。
实际示例:批量处理
这个实际示例在批量处理场景中展示了 slices.Grow,在这种场景下我们知道结果的大致大小。
package main
import (
"fmt"
"math/rand"
"slices"
"time"
)
func processItem(i int) float64 {
time.Sleep(time.Microsecond) // Simulate work
return rand.Float64() * float64(i)
}
func main() {
const batchSize = 10_000
results := make([]float64, 0)
// Pre-allocate for expected results
results = slices.Grow(results, batchSize)
start := time.Now()
for i := 0; i < batchSize; i++ {
results = append(results, processItem(i))
}
fmt.Printf("Processed %d items in %v\n",
len(results), time.Since(start))
fmt.Println("Final capacity:", cap(results))
}
在处理之前增长切片,可以避免在批量操作过程中发生重新分配。这种优化在性能关键的代码中尤其有价值。
来源
本教程通过各种场景下的实际示例,介绍了 Go 中 slices.Grow 函数以及高效的切片容量管理。