我一直在读Go,并且难以想到这个基本问题。
在Go中,很明显切片更灵活,通常可以在需要数据序列时用来代替数组。
阅读大部分文档,他们似乎鼓励开发人员只使用切片而不是数组。我得到的印象就像创作者可以简单地设计数组来调整大小,并且没有整个切片部分。事实上,这样的设计会使语言更容易理解,甚至可能鼓励更多惯用代码。
那么为什么创作者首先允许数组呢?何时使用数组而不是切片?是否存在使用数组而非引人注目的情况?
当我查阅官方文件(http://golang.org/doc/effective_go.html#arrays)时,我找到的唯一有用的部分是:
在规划内存和内存的详细布局时,数组非常有用 有时可以帮助避免分配,但主要是它们是一个构建块 切片。
他们接着讨论了数组如何作为值昂贵,以及如何使用指针模拟C风格的行为。即便如此,他们还是明确地推荐了阵列部分:
但即使这种风格也不是惯用的Go。改为使用切片。
那么,有哪些真实例子可以规划内存的详细布局"或者"帮助避免分配"切片不适合?
答案 0 :(得分:16)
正如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}
然后为它创建一个序列化函数。 Hashable数组意味着您可以将其称为[16]字节。
听起来更接近C&C的malloc,sizeof
不,这与malloc或sizeof无关。那些是分配内存并获得变量的大小。
然而,CGo是另一个用例。 cgo命令创建与其对应的C类型具有相同内存布局的类型。为此,有时需要插入未命名的数组以进行填充。
如果问题可以通过使用切片来解决......零/不显着的性能损失......
Arrays还可以防止间接更快地创建某些类型的代码。当然,这是一个很小的优化,几乎在所有情况下这都是微不足道的。
答案 1 :(得分:6)
一个实际差异是arrays
是可以播放的,而slices
则不是。
答案 2 :(得分:6)
补充Stephen Weinberg的回答:
那么,什么是“规划内存的详细布局”或“帮助避免分配”这些切片不适合的实际例子呢?
以下是“规划内存详细布局”的示例。有许多文件格式。通常文件格式是这样的:它以"magic number"开头,然后是信息头,其结构通常是固定的。此标题包含有关内容的信息,例如在图像文件的情况下,它包含图像大小(宽度,高度),像素格式,使用的压缩,标题大小,图像数据偏移等相关信息(基本上描述了文件的其余部分)以及如何解释/处理它。)
如果要在Go中实现文件格式,一种简单方便的方法是创建包含格式标题字段的struct
。如果要读取此类格式的文件,可以使用binary.Read()
方法将整个标题struct
读入变量,类似地,当您要编写该格式的文件时,可以使用binary.Write()
将完整的标题一步写入文件(或者您发送数据的任何地方)。
标题可能包含几十或一百个字段,您仍然可以通过一次方法调用来读/写它。
现在您可以感觉到,标题struct
的“内存布局”必须与字节布局完全匹配,因为如果您想在一个文件中保存(或应该保存)它步骤
许多文件格式通常很复杂,因为它们希望是通用的,因此允许广泛的用途和功能。很多时候你不想实现/处理格式支持的所有东西,因为要么你不关心(因为你只想提取一些信息),要么你不必因为你有保证输入只会使用子集或固定格式(在文件格式完全支持的许多情况下)。
那么,如果你有一个包含许多字段的标题规范,但是你只需要其中一些字段,你会怎么做?您可以定义一个包含所需字段的结构,在字段之间可以使用具有您不关心/不需要的字段大小的数组。这将确保您仍然可以通过一个函数调用读取整个标头,并且数组基本上将是文件中未使用数据的占位符。如果您不使用数据,也可以使用blank标识符作为标题struct
定义中的字段名称。
举一个简单的例子,让我们实现一种格式,其中魔术是“TGI”(理论Go图像),标题包含这样的字段:2个保留字(每个16位),1个双字图像宽度,1个双字图像高度,现在来了15“不关心”dwords然后图像节省时间为自1970年1月1日UTC以来的8字节纳秒。
这可以使用这样的结构建模(不包括幻数):
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
}
答案 3 :(得分:0)
要扩展此
在规划内存和内存的详细布局时,数组非常有用 有时可以帮助避免分配,但主要是他们是一个建筑物 切片。
在考虑堆分配的开销时,数组可以更有效。想想垃圾收集器,堆管理和碎片等等。
例如,如果你有一个像var x [8]int
这样的本地数组变量,在函数返回后没有使用,很可能它会在堆栈上分配。
堆栈分配比堆分配便宜得多。
对于嵌套结构,例如结构中的数组或数组的数组,将它们分配到一个blob而不是几个部分中会更便宜。
因此,将数组用于固定大小的相对较短的序列,例如一个IP地址。