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
函数以及高效的切片容量管理。