在Go中创建2D切片的简洁方法是什么?

问题描述 投票:51回答:3

我正在通过A Tour of Go学习Go。其中一个练习要求我创建一个包含dydx行和uint8列的2D切片。我目前采用的方法是:

a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

我认为迭代每个切片来初始化它太冗长了。如果切片具有更多尺寸,则代码将变得难以处理。是否有一种简洁的方法来初始化Go中的2D(或n维)切片?

go slice
3个回答
86
投票

没有更简洁的方式,你所做的是“正确”的方式;因为切片总是一维的,但可以组成以构造更高维的对象。有关更多详细信息,请参阅此问题:Go: How is two dimensional array's memory representation

你可以简化它的一件事是使用for range结构:

a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

另请注意,如果使用composite literal初始化切片,则可以“免费”获取此切片,例如:

a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

是的,这有其局限性,因为看似你必须列举所有元素;但是有一些技巧,即你不必枚举所有的值,只需要那些不是切片元素类型的zero values的值。有关此内容的更多详细信息,请参阅Keyed items in golang array initialization

例如,如果你想要一个前10个元素为零的切片,然后是12,它可以像这样创建:

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

另请注意,如果您使用arrays而不是slices,则可以非常轻松地创建它:

c := [5][5]uint8{}
fmt.Println(c)

输出是:

[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

对于数组,您不必遍历“外部”数组并初始化“内部”数组,因为数组不是描述符而是值。有关更多详细信息,请参阅博客文章Arrays, slices (and strings): The mechanics of 'append'

试试Go Playground上的例子。


5
投票

有两种方法可以使用切片来创建矩阵。我们来看看它们之间的差异。

第一种方法:

matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

第二种方法:

matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

关于第一种方法,进行连续的make调用并不能确保您最终得到一个连续的矩阵,因此您可能将矩阵划分为内存。让我们想一个两个Go例程可能导致这种情况的例子:

  1. 例程#0运行make([][]int, n)matrix获取分配的内存,从0x000到0x07F获取一块内存。
  2. 然后,它启动循环并执行第一行make([]int, m),从0x080到0x0FF。
  3. 在第二次迭代中,它被调度程序抢占。
  4. 调度程序为处理器提供例程#1并开始运行。这个也使用make(为了它自己的目的)并从0x100到0x17F(紧邻例程#0的第一行)。
  5. 一段时间后,它被抢占,例程#0再次开始运行。
  6. 它执行与第二次循环迭代相对应的make([]int, m),并从第二行的0x180到0x1FF。此时,我们已经有两个分开的行。

使用第二种方法,例程执行make([]int, n*m)以获得在单个切片中分配的所有矩阵,从而确保连续性。之后,需要循环来更新矩阵指针到对应于每行的子对象。

您可以使用上面Go Playground中显示的代码来查看使用这两种方法分配的内存的差异。请注意,我仅使用runtime.Gosched()来产生处理器并强制调度程序切换到另一个例程。

哪一个使用?想象一下第一种方法的最坏情况,即每一行在内存中不是下一行的下一行。然后,如果您的程序遍历矩阵元素(读取或写入它们),则与第二种方法相比,可能会有更多的缓存未命中(因此延迟更高),因为数据局部性更差。另一方面,使用第二种方法可能无法获得为矩阵分配的单个内存,因为内存碎片(块遍布内存),即使理论上可能有足够的可用内存。

因此,除非存在大量内存碎片并且要分配的矩阵足够大,否则您总是希望使用第二种方法来获取数据局部性。


0
投票

你可以参考这段代码 -

package main

import "fmt"

func main() {
    var row, col int
    fmt.Print("enter rows cols: ")
    fmt.Scan(&row, &col)

    // allocate composed 2d array
    a := make([][]int, row)
    for i := range a {
        a[i] = make([]int, col)
    }

    // array elements initialized to 0
    fmt.Println("a[0][0] =", a[0][0])

    // assign
    a[row-1][col-1] = 7

    // retrieve
    fmt.Printf("a[%d][%d] = %d\n", row-1, col-1, a[row-1][col-1])

    // remove only reference
    a = nil
    // memory allocated earlier with make can now be garbage collected.
}

Reference

© www.soinside.com 2019 - 2024. All rights reserved.