【问题标题】:Why use arrays instead of slices?为什么使用数组而不是切片?
【发布时间】:2015-08-22 01:32:13
【问题描述】:

我一直在阅读有关 Go 的文章,并在思考这个基本问题时感到困惑。

在 Go 中,很明显切片更加灵活,当您需要数据序列时,通常可以使用切片代替数组。

阅读大部分文档后,他们似乎在鼓励开发人员只使用切片而不是数组。我得到的印象是创建者可以简单地将数组设计为可调整大小,并且无需整个切片部分即可完成。事实上,这样的设计会让语言更容易理解,甚至可能鼓励更多地道的代码。

那么为什么创建者一开始就允许使用数组呢?什么时候会使用数组而不是切片?在切片上使用数组会令人信服吗?

当我查阅官方文档(http://golang.org/doc/effective_go.html#arrays)时,我发现唯一有用的部分是:

数组在规划内存的详细布局时很有用 有时可以帮助避免分配,但主要是它们是一个构建块 切片。

他们接着讨论了数组作为值是多么昂贵,以及如何使用指针模拟 C 风格的行为。即便如此,他们还是以明确的建议结束了数组部分:

但即使是这种风格也不是惯用的 Go。请改用切片。

那么,切片不适合的“规划内存的详细布局”或“帮助避免分配”的真实示例有哪些?

【问题讨论】:

  • 比萨最好切片食用

标签: arrays go slice


【解决方案1】:

正如 Akavall 所说,数组是可散列的。这意味着它们可以用作地图的键。

它们也是按值传递的。每次将它传递给函数或将其分配给另一个变量时,它都会对其进行完整的复制。

它们可以通过编码/二进制进行序列化。

它们也可以用来控制内存布局。由于它不是引用,因此当它被放置在结构中时,它将分配那么多内存作为结构的一部分,而不是像切片那样在其中放置相当于指针的内存。

底线,除非你知道自己在做什么,否则不要使用数组。


Hashable/serializable 都很不错,但我只是不确定它们是否真的那么引人注目

如果您想要一个 md5 哈希映射,您会怎么做?不能使用字节片,所以你需要做这样的事情来绕过类型系统:

// 16 bytes
type hashableMd5 struct {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p byte}

然后为它创建一个序列化函数。可哈希数组意味着您可以将其称为 [16] 字节。

听起来好像更接近C的malloc,sizeof

不,这与 malloc 或 sizeof 无关。这些是分配内存并获取变量的大小。

但是,CGo 是另一个用例。 cgo 命令创建的类型与其对应的 C 类型具有相同的内存布局。为此,它有时需要插入未命名的数组进行填充。

如果问题可以通过...解决...零/使用切片的性能损失不显着...

数组还可以防止间接使某些类型的代码更快。当然,这是一个很小的优化,几乎在所有情况下都无关紧要。

【讨论】:

  • 嗨,斯蒂芬,感谢您的回答。 Hashable/serializable 都很好,但我只是不确定它们是否真的那么引人注目。对于用于控制内存布局的数组,你有一个真实的例子来说明你为什么要这样做吗?听起来像是越来越接近 C 的 malloc,sizeof。是的,我相信 Google 的底线与您的底线相同——也就是说,或多或少,不要使用数组。如果使用切片可以用更少的难度和零/微不足道的性能损失来解决问题,我想知道为什么它们首先包含数组。
  • 感谢哈希地图示例 - 这真的很有帮助。
  • 我仍然不太了解“控制内存布局”部分,但听起来相当低级。我认为这可能不是普通开发人员在决定切片和数组时需要担心的事情,除非是非常具体的用例。我想我们可以得出结论:“只要可行,就使用数组,仅当切片不那么容易(例如哈希映射)或者您正在做一些低级工作并且您需要固定内存分配的特性时才使用数组。”准确吗?
【解决方案2】:

补充斯蒂芬温伯格的回答:

那么,切片不适合的“规划内存的详细布局”或“帮助避免分配”的真实示例有哪些?

这里是一个“规划内存的详细布局”的例子。有许多文件格式。通常文件格式是这样的:它以"magic number" 开头,然后是一个信息性标题,其结构通常是固定的。此标头包含有关内容的信息,例如在图像文件的情况下,它包含图像大小(宽度、高度)、像素格式、使用的压缩、标头大小、图像数据偏移等信息(基本上描述了文件的其余部分以及如何解释/处理它)。

如果你想在 Go 中实现一种文件格式,一个简单方便的方法是创建一个包含格式头字段的struct。当你想读取这种格式的文件时,你可以使用binary.Read()方法将整个标题struct读入一个变量,同样当你想写入这种格式的文件时,你可以使用binary.Write()一步将完整的标头写入文件(或发送数据的任何位置)。

标头可能包含数十个或一百个字段,您仍然可以通过一个方法调用来读取/写入它。

现在您可以感觉到,如果您想一步完成所有操作,标头struct 的“内存布局”必须与文件中保存(或应该保存)的字节布局完全匹配。

数组在哪里出现?

许多文件格式通常很复杂,因为它们希望具有通用性,因此允许广泛的用途和功能。很多时候,您不想实现/处理格式支持的所有内容,因为您不关心(因为您只想提取一些信息),或者您不必这样做,因为您可以保证输入只会使用子集或固定格式(文件格式完全支持的许多情况)。

如果您有一个包含许多字段但只需要其中几个字段的标头规范,您会怎么做?您可以定义一个包含您需要的字段的结构,并且在字段之间您可以使用具有您不关心/不需要的字段大小的数组。这将确保您仍然可以通过一个函数调用读取整个标题,并且数组基本上将成为文件中未使用数据的占位符。如果您不使用数据,也可以使用 blank 标识符作为标头 struct 定义中的字段名称。

理论例子

举个简单的例子,让我们实现一个格式,其中魔法是“TGI”(Theoretical Go Image),并且标题包含如下字段:2 个保留字(每个 16 位)、1 个 dword 图像宽度、1 个 dword 图像高度,现在有 15 个“无关”双字,然后图像将时间保存为 8 字节,即自 1970 年 1 月 1 日 UTC 以来的纳秒。

这可以用这样的结构建模(不包括幻数):

type TGIHeader struct {
    _        uint16 // Reserved
    _        uint16 // Reserved
    Width    uint32
    Height   uint32
    _        [15]uint32 // 15 "don't care" dwords
    SaveTime int64
}

读取 TGI 文件并打印有用信息:

func ShowInfo(name string) error {
    f, err := os.Open(name)
    if err != nil {
        return err
    }
    defer f.Close()

    magic := make([]byte, 3)
    if _, err = f.Read(magic); err != nil {
        return err
    }
    if !bytes.Equal(magic, []byte("TGI")) {
        return errors.New("Not a TGI file")
    }

    th := TGIHeader{}
    if err = binary.Read(f, binary.LittleEndian, &th); err != nil {
        return err
    }

    fmt.Printf("%s is a TGI file,\n\timage size: %dx%d\n\tsaved at: %v",
        name, th.Width, th.Height, time.Unix(0, th.SaveTime))

    return nil
}

【讨论】:

  • 这正是 cgo 命令在创建与其 C 对应物匹配的 Go 结构时所做的事情。赞成。
【解决方案3】:

一个实际的区别是arrays 是可散列的,而slices 不是。

【讨论】:

  • 感谢您的回答。我没有足够的经验来知道可散列的数组是否必要或可取。无论如何,我猜大多数结构都没有可散列的等价物?
  • 结构体如果包含不可散列的字段,则不可散列。
【解决方案4】:

对此进行扩展

数组在规划内存的详细布局时很有用 有时可以帮助避免分配,但主要是它们是建筑物 切片。

考虑堆分配的开销时,数组可能更有效。想想垃圾收集器、堆管理和碎片等。

例如,如果你有一个像var x [8]int 这样的局部数组变量在函数返回后没有使用,很可能它会被分配到堆栈上。 而且堆栈分配比堆分配便宜得多。

对于嵌套结构,如数组的数组或结构内的数组,将它们分配在一个 blob 中而不是多个块中更便宜。

因此,将数组用于固定大小的相对较短的序列,例如IP 地址。

【讨论】:

    猜你喜欢
    • 2021-06-25
    • 1970-01-01
    • 1970-01-01
    • 2015-11-17
    • 2021-06-30
    • 2019-10-22
    • 2017-06-19
    • 2017-07-27
    • 1970-01-01
    相关资源
    最近更新 更多