二维数组的内存表示如何

时间:2016-09-18 18:24:15

标签: arrays go slice

在Java中,正如我们所知,二维数组是多维一维数组。这意味着那些一维数组在内存上不连续。

相反,在C中,二维数组实际上是一维数组,其大小为 total_row * total_column 。因为Go语言使用C语言中的许多概念。所以我的问题是:Go中的二维数组的内存表示看起来像是用C还是Java?

2 个答案:

答案 0 :(得分:21)

在Go中,slices经常被误认为arrays,所以我回答两者。

阵列

spec: Array types:

引用
  

数组类型总是一维的,但可以组合成多维类型。

你的回答清楚明了。但答案不仅仅是因为数组是Go中的值,它们不是像切片那样的描述符(标题)。

见这个简单的例子:

x := [5][5]byte{}
fmt.Println(&x[0][3])
fmt.Println(&x[0][4])
fmt.Println(&x[1][0])

输出(在Go Playground上尝试):

0x10432203
0x10432204
0x10432205

如您所见,为数组分配和使用的内存是连续的:第二行从内存地址开始,该内存地址是第一行最后一个元素的地址之后。

检查数组的大小:

fmt.Println(unsafe.Sizeof([4][6]int{})) // 96
fmt.Println(unsafe.Sizeof([6][4]int{})) // 96

如果切换行和列并不重要,它的大小是相同的。

切片

在切片的情况下也是如此:多维切片是切片切片。 Spec: Slice types:

  

切片是基础数组的连续段的描述符,并提供对该数组中编号的元素序列的访问。
  ...
  与数组类似,切片始终是一维的,但可以组合以构造更高维的对象。

切片是描述符,切片头包含指向底层(支持)数组元素的指针,长度和容量。因此总切片的数量在内存使用方面很重要。

见这个例子:

x := make([][]byte, 2)
for i := range x {
    x[i] = make([]byte, 1000)
}
fmt.Println(len(x), len(x)*len(x[0]))

y := make([][]byte, 1000)
for i := range y {
    y[i] = make([]byte, 2)
}
fmt.Println(len(y), len(y)*len(y[0]))

输出(在Go Playground上尝试):

2 2000
1000 2000

xy多维切片总共有2000个元素(2000个字节),但x只存储2个切片,每个切片都有1000个元素,同时y存储1000个切片,每个切片都有2个元素。

这意味着x需要2切片标头,而y需要1000切片标头(对于元素,+1为x和{{1} }他们自己)!

切片标头由reflect.SliceHeader表示:

y

32位体系结构上的片头大小为12字节,64位体系结构上的片大小为24字节。因此,如果type SliceHeader struct { Data uintptr Len int Cap int } 的32位arch元素需要2,000字节加上内存中的2x12字节 2,024字节,而x的元素需要2,000字节加上1,000 * 12这是 14,000字节

另请注意,多维切片的元素可能包含不同长度的切片:

  

对于数组数组,内部数组通过构造始终具有相同的长度;然而,对于切片(或切片阵列),内部长度可以动态变化。此外,内部切片必须单独初始化。

就像这个例子一样:

y

输出(在Go Playground上尝试):

x := make([][]byte, 2)
x[0] = []byte{1, 2}
x[1] = []byte{1, 2, 3, 4}
fmt.Println(x[0])
fmt.Println(x[1])

如果您还没有阅读,建议:The Go Blog: Arrays, slices (and strings): The mechanics of 'append'

答案 1 :(得分:0)

对我来说,Go 中的 slice 就像 C++ 中的 vectorVector 也有 3 个关键数据成员:指向数据的 ptr、容量和长度。如果 vector 是 2d,则子向量的内部长度也可能动态变化。

相比之下,Go 中的数组就像 C 中的数组。