ZetCode

Golang slices.Grow

最后修改于 2025 年 4 月 20 日

本教程将介绍如何在 Go 中使用 slices.Grow 函数。我们将通过实际示例讲解切片容量管理。

slices.Grow 函数可以增加切片的容量,以保证有足够的空间容纳额外的元素。它是 Go 实验性 slices 包的一部分。

当您需要高效地追加大量元素时,此函数非常有用。它通过提前预分配容量来最大限度地减少内存分配。

基本的 slices.Grow 示例

slices.Grow 最简单的用法是为未来的追加操作准备切片。我们指定了预期要添加的额外元素的数量。

basic_grow.go
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 可用于空切片。此示例演示了如何为新切片预先分配容量。

empty_grow.go
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 将不执行任何操作。此示例演示了此行为。

sufficient_capacity.go
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 个元素不会改变任何内容。函数会返回原始切片。

增长超出当前容量

当增长超出当前容量时,切片会获得一个新的底层数组。此示例展示了内存分配行为。

beyond_capacity.go
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 会分配一个新的数组。容量会增长,以同时容纳现有元素和请求的增长。

性能比较

此示例比较了增长与重复追加的性能。它展示了预先分配的性能优势。

performance.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 可用于任何类型的切片。此示例演示了如何增长自定义结构体切片。

struct_slice.go
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,在这种场景下我们知道结果的大致大小。

batch_processing.go
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 实验性切片包文档

本教程通过各种场景下的实际示例,介绍了 Go 中 slices.Grow 函数以及高效的切片容量管理。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程