为什么使用数组而不是切片?
Why use arrays instead of slices?
我一直在阅读有关 Go 的文章,但在思考这个基本问题时遇到了困难。
在 Go 中,很明显切片更灵活,当您需要一个数据序列时,通常可以使用它来代替数组。
阅读大部分文档,他们似乎在鼓励开发人员只使用切片而不是数组。我的印象是创作者可以简单地将数组设计为可调整大小,并且无需整个切片部分即可完成。事实上,这样的设计会让语言更容易理解,甚至可能鼓励更多地道的代码。
那么为什么创作者一开始就允许使用数组呢?什么时候会使用数组而不是切片?在切片上使用数组是否会令人信服?
在查阅官方文档(http://golang.org/doc/effective_go.html#arrays)时,我发现唯一有用的部分是:
Arrays are useful when planning the detailed layout of memory and
sometimes can help avoid allocation, but primarily they are a building block
for slices.
他们继续讨论数组作为值如何昂贵,以及如何使用指针模拟 C 风格的行为。即便如此,他们还是以明确的建议结束了数组部分:
But even this style isn't idiomatic Go. Use slices instead.
那么,切片不适合的 "planning the detailed layout of memory" 或 "help avoid allocation" 的一些真实示例是什么?
一个实际区别是 arrays
是可散列的,而 slices
不是。
正如 Akavall 所说,数组是可哈希的。这意味着它们可以用作地图的键。
它们也是按值传递的。每次将它传递给函数或将其分配给另一个变量时,它都会制作一个完整的副本。
它们可以被encoding/binary序列化。
它们也可以用来控制内存布局。由于它不是引用,当它被放置在结构中时,它将分配那么多内存作为结构的一部分,而不是像切片那样将指针的等价物放在那里。
最重要的是,除非您知道自己在做什么,否则不要使用数组。
Hashable/serializable are all nice to have, but I'm just not sure if they are indeed that compelling to have
如果你想要一张 md5 哈希表,你会怎么做?不能使用字节切片,所以你需要做这样的事情来绕过类型系统:
// 16 bytes
type hashableMd5 struct {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p byte}
然后为它创建一个序列化函数。可哈希数组意味着您可以将其称为 [16]byte.
Sounds like getting closer to C's malloc, sizeof
不,这与 malloc 或 sizeof 无关。那些是分配内存和获取变量的大小。
然而,CGo 是另一个用例。 cgo 命令创建的类型与其对应的 C 类型具有相同的内存布局。为此,它有时需要插入未命名的数组进行填充。
If problems can be solved with ... nil/insignificant performance penalty using slices ...
数组还可以防止间接使某些类型的代码更快。当然,这是一个很小的优化,几乎在所有情况下都是微不足道的。
补充 Stephen Weinberg 的回答:
So, what are some real examples of "planning the detailed layout of memory" or "help avoid allocation" that slices would be unsuited for?
这是 "planning the detailed layout of memory" 的示例。文件格式有很多种。通常文件格式是这样的:它以 "magic number" 开头,然后是一个信息性的 header,其结构通常是固定的。 header 包含有关内容的信息,例如,在图像文件的情况下,它包含图像大小(宽度、高度)、像素格式、使用的压缩、header 大小、图像数据偏移等信息(基本上描述了文件的其余部分以及如何解释/处理它)。
如果你想在 Go 中实现一个文件格式,一个简单方便的方法是创建一个 struct
包含格式的 header 字段。当你想读取这种格式的文件时,你可以使用 binary.Read()
method to read the whole header struct
into a variable, and similarly when you want to write a file of that format, you can use binary.Write()
将完整的 header 一步写入文件(或任何你发送数据的地方)。
header可能包含几十个或一百个字段,你仍然可以read/write它只需要一个方法调用。
现在您可以感觉到,header struct
的 "memory layout" 必须与文件中保存(或应该保存)的字节布局完全匹配,如果您想一步搞定。
数组在哪里出现?
许多文件格式通常都很复杂,因为它们想要通用,因此允许广泛的用途和功能。很多时候你不想实现/处理格式支持的所有内容,因为要么你不关心(因为你只想提取一些信息),要么你不必因为你保证输入只会使用子集或固定格式(在文件格式完全支持的许多情况下)。
那么,如果您的 header 规范包含许多字段但您只需要其中的几个字段,您会怎么做?您可以定义一个包含所需字段的结构,并且可以在字段之间使用数组,数组的大小与您不关心/不需要的字段相同。这将确保您仍然可以通过一次函数调用读取整个 header,并且数组基本上是文件中未使用数据的占位符。如果您不使用数据,也可以使用 blank 标识符作为 header struct
定义中的字段名称。
理论例子
举个简单的例子,让我们实现一种格式,其中魔法是 "TGI"(理论围棋图像),header 包含这样的字段:2 个保留字(每个 16 位),1 dword 图像宽度,1 dword 图像高度,现在是 15 "don't care" dwords 然后图像保存时间为 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
}
对此进行扩展
Arrays are useful when planning the detailed layout of memory and
sometimes can help avoid allocation, but primarily they are a building
block for slices.
考虑到堆分配的开销,数组可以更有效。想想垃圾收集器、堆管理和碎片等
例如,如果你有一个像var x [8]int
这样的局部数组变量,它在函数returns之后没有被使用,它很可能会被分配到堆栈上。
而且堆栈分配比堆分配便宜得多。
同样对于嵌套结构,如数组的数组或结构内的数组,将它们分配到一个 blob 中比分配到多个块中更便宜。
因此,对于固定大小的相对较短的序列使用数组,例如IP 地址。
我一直在阅读有关 Go 的文章,但在思考这个基本问题时遇到了困难。
在 Go 中,很明显切片更灵活,当您需要一个数据序列时,通常可以使用它来代替数组。
阅读大部分文档,他们似乎在鼓励开发人员只使用切片而不是数组。我的印象是创作者可以简单地将数组设计为可调整大小,并且无需整个切片部分即可完成。事实上,这样的设计会让语言更容易理解,甚至可能鼓励更多地道的代码。
那么为什么创作者一开始就允许使用数组呢?什么时候会使用数组而不是切片?在切片上使用数组是否会令人信服?
在查阅官方文档(http://golang.org/doc/effective_go.html#arrays)时,我发现唯一有用的部分是:
Arrays are useful when planning the detailed layout of memory and sometimes can help avoid allocation, but primarily they are a building block for slices.
他们继续讨论数组作为值如何昂贵,以及如何使用指针模拟 C 风格的行为。即便如此,他们还是以明确的建议结束了数组部分:
But even this style isn't idiomatic Go. Use slices instead.
那么,切片不适合的 "planning the detailed layout of memory" 或 "help avoid allocation" 的一些真实示例是什么?
一个实际区别是 arrays
是可散列的,而 slices
不是。
正如 Akavall 所说,数组是可哈希的。这意味着它们可以用作地图的键。
它们也是按值传递的。每次将它传递给函数或将其分配给另一个变量时,它都会制作一个完整的副本。
它们可以被encoding/binary序列化。
它们也可以用来控制内存布局。由于它不是引用,当它被放置在结构中时,它将分配那么多内存作为结构的一部分,而不是像切片那样将指针的等价物放在那里。
最重要的是,除非您知道自己在做什么,否则不要使用数组。
Hashable/serializable are all nice to have, but I'm just not sure if they are indeed that compelling to have
如果你想要一张 md5 哈希表,你会怎么做?不能使用字节切片,所以你需要做这样的事情来绕过类型系统:
// 16 bytes
type hashableMd5 struct {a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p byte}
然后为它创建一个序列化函数。可哈希数组意味着您可以将其称为 [16]byte.
Sounds like getting closer to C's malloc, sizeof
不,这与 malloc 或 sizeof 无关。那些是分配内存和获取变量的大小。
然而,CGo 是另一个用例。 cgo 命令创建的类型与其对应的 C 类型具有相同的内存布局。为此,它有时需要插入未命名的数组进行填充。
If problems can be solved with ... nil/insignificant performance penalty using slices ...
数组还可以防止间接使某些类型的代码更快。当然,这是一个很小的优化,几乎在所有情况下都是微不足道的。
补充 Stephen Weinberg 的回答:
So, what are some real examples of "planning the detailed layout of memory" or "help avoid allocation" that slices would be unsuited for?
这是 "planning the detailed layout of memory" 的示例。文件格式有很多种。通常文件格式是这样的:它以 "magic number" 开头,然后是一个信息性的 header,其结构通常是固定的。 header 包含有关内容的信息,例如,在图像文件的情况下,它包含图像大小(宽度、高度)、像素格式、使用的压缩、header 大小、图像数据偏移等信息(基本上描述了文件的其余部分以及如何解释/处理它)。
如果你想在 Go 中实现一个文件格式,一个简单方便的方法是创建一个 struct
包含格式的 header 字段。当你想读取这种格式的文件时,你可以使用 binary.Read()
method to read the whole header struct
into a variable, and similarly when you want to write a file of that format, you can use binary.Write()
将完整的 header 一步写入文件(或任何你发送数据的地方)。
header可能包含几十个或一百个字段,你仍然可以read/write它只需要一个方法调用。
现在您可以感觉到,header struct
的 "memory layout" 必须与文件中保存(或应该保存)的字节布局完全匹配,如果您想一步搞定。
数组在哪里出现?
许多文件格式通常都很复杂,因为它们想要通用,因此允许广泛的用途和功能。很多时候你不想实现/处理格式支持的所有内容,因为要么你不关心(因为你只想提取一些信息),要么你不必因为你保证输入只会使用子集或固定格式(在文件格式完全支持的许多情况下)。
那么,如果您的 header 规范包含许多字段但您只需要其中的几个字段,您会怎么做?您可以定义一个包含所需字段的结构,并且可以在字段之间使用数组,数组的大小与您不关心/不需要的字段相同。这将确保您仍然可以通过一次函数调用读取整个 header,并且数组基本上是文件中未使用数据的占位符。如果您不使用数据,也可以使用 blank 标识符作为 header struct
定义中的字段名称。
理论例子
举个简单的例子,让我们实现一种格式,其中魔法是 "TGI"(理论围棋图像),header 包含这样的字段:2 个保留字(每个 16 位),1 dword 图像宽度,1 dword 图像高度,现在是 15 "don't care" dwords 然后图像保存时间为 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
}
对此进行扩展
Arrays are useful when planning the detailed layout of memory and sometimes can help avoid allocation, but primarily they are a building block for slices.
考虑到堆分配的开销,数组可以更有效。想想垃圾收集器、堆管理和碎片等
例如,如果你有一个像var x [8]int
这样的局部数组变量,它在函数returns之后没有被使用,它很可能会被分配到堆栈上。
而且堆栈分配比堆分配便宜得多。
同样对于嵌套结构,如数组的数组或结构内的数组,将它们分配到一个 blob 中比分配到多个块中更便宜。
因此,对于固定大小的相对较短的序列使用数组,例如IP 地址。