Golang中如何重新分配切片变量

Golang中如何重新分配切片变量 以下代码可以运行,但感觉有些不对劲。

我有一个 [][]string 切片(下面称为“outer”)。 在一个循环中,我想计算一个 []string 切片(下面称为“inner”),并将其添加到上面的“outer”中。

简化代码如下:

package main
import "fmt"
func main() {
   var outer [][]string
   var inner []string
   inner = []string{}
   // 以下是简化版本:实际上 inner 的填充
   // 不仅仅是赋值:
   inner = append(inner, aa, bb, cc)
   outer = append(outer, inner)
   inner = []string{ba, bb, bc}
   outer = append(outer, inner)
   inner = []string{ca, cb, cc, cd}
   outer = append(outer, inner)
   fmt.Println(outer)
}

也许我在某些情况下对切片的“指针性质”感到困惑,也就是说,它们是通过引用传递的(尽管有固定的头部/容量)。

问题: “inner”切片底层的数组会发生什么?每次我将“inner”重新赋值为一个新值时,是否会创建一个新数组? 这样做开销大吗? 我看到的唯一替代方案是预先确定切片的大小(这不太容易,因为我在开始循环时不知道所需的容量),然后像这样分配各个字段:

outer[0][0] = “aa”

更多关于Golang中如何重新分配切片变量的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

谢谢。不用担心:你提出的解决方案对我来说很清晰。 在新的“内部”元素比前一次迭代中的少的情况下,我只需要截断内部“传承”切片元素即可。

更多关于Golang中如何重新分配切片变量的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好!由于你没有任何关于容量的信息,你必须在每次操作时都追加内部切片,而在你的代码中,每次想要覆盖 inner 时都会重新分配它。一个更好的解决方案可能是控制新内部切片的元素数量是否大于上一个内部切片的容量,并在必要时分配一个新的切片;而你目前的做法总是如此。考虑第二种情况(我们称之为案例2): 你将新元素分配给 inner 的范围,从索引0到新的所需容量……然后从刚刚被覆盖的 inner 切片返回一个新的切片(这是一个高效的操作),接着将其追加到 outer 中。通过这种方式,你可以节省所有与新的所需容量小于或等于旧内部切片容量相关的分配,并且只将你想要的切片追加到 outer 中。 如果你不理解,请告诉我 🙂

在Go中,切片是引用类型,底层确实是一个数组。你的代码是正确的,每次重新赋值inner = []string{...}都会创建一个新的底层数组。

底层数组的变化

package main
import "fmt"

func main() {
    var outer [][]string
    var inner []string
    
    // 第一次:创建新的底层数组
    inner = []string{"aa", "bb", "cc"}
    fmt.Printf("inner地址: %p, 底层数组地址: %p\n", &inner, &inner[0])
    outer = append(outer, inner)
    
    // 第二次:创建新的底层数组
    inner = []string{"ba", "bb", "bc"}
    fmt.Printf("inner地址: %p, 底层数组地址: %p\n", &inner, &inner[0])
    outer = append(outer, inner)
    
    // 第三次:创建新的底层数组
    inner = []string{"ca", "cb", "cc", "cd"}
    fmt.Printf("inner地址: %p, 底层数组地址: %p\n", &inner, &inner[0])
    outer = append(outer, inner)
    
    fmt.Println("outer:", outer)
}

输出示例:

inner地址: 0xc000004078, 底层数组地址: 0xc000010240
inner地址: 0xc000004078, 底层数组地址: 0xc000010270  
inner地址: 0xc000004078, 底层数组地址: 0xc0000102a0

可以看到:

  1. inner变量的地址不变(栈上的切片头)
  2. 每次[]string{...}都创建了新的底层数组(不同的地址)

性能考虑

你的方法开销不大,因为:

  1. 切片头很小(24字节:指针+长度+容量)
  2. 数组分配在堆上,由GC管理
  3. 每次创建新数组避免了共享底层数组的副作用

替代方案对比

// 方法1:你的当前方法(推荐)
func method1() [][]string {
    var outer [][]string
    var inner []string
    
    inner = []string{"aa", "bb", "cc"}
    outer = append(outer, inner)
    
    inner = []string{"ba", "bb", "bc"}
    outer = append(outer, inner)
    
    return outer
}

// 方法2:直接append到outer(更简洁)
func method2() [][]string {
    var outer [][]string
    
    outer = append(outer, []string{"aa", "bb", "cc"})
    outer = append(outer, []string{"ba", "bb", "bc"})
    
    return outer
}

// 方法3:使用make预分配(如果知道大小)
func method3() [][]string {
    outer := make([][]string, 0, 3) // 预分配容量
    
    outer = append(outer, []string{"aa", "bb", "cc"})
    outer = append(outer, []string{"ba", "bb", "bc"})
    outer = append(outer, []string{"ca", "cb", "cc"})
    
    return outer
}

// 方法4:循环中重用切片(注意:需要copy避免数据覆盖)
func method4() [][]string {
    var outer [][]string
    inner := make([]string, 0, 4) // 预分配容量
    
    // 第一组
    inner = append(inner, "aa", "bb", "cc")
    outer = append(outer, append([]string{}, inner...)) // 必须复制
    inner = inner[:0] // 清空切片,重用底层数组
    
    // 第二组
    inner = append(inner, "ba", "bb", "bc")
    outer = append(outer, append([]string{}, inner...))
    inner = inner[:0]
    
    return outer
}

循环中的正确用法

func processData(data [][]string) [][]string {
    var result [][]string
    
    for _, row := range data {
        // 每次循环创建新的切片
        newRow := make([]string, len(row))
        copy(newRow, row) // 如果需要修改
        
        // 或者直接创建新切片
        // newRow := []string{row[0], row[1]}
        
        result = append(result, newRow)
    }
    
    return result
}

你的代码是正确的,每次inner = []string{...}都会创建新的底层数组,这是安全且推荐的做法,避免了切片共享底层数组可能导致的意外修改。性能开销在大多数情况下可以接受。

回到顶部