多维数组和 C# 中的数组数组有啥区别?

Posted

技术标签:

【中文标题】多维数组和 C# 中的数组数组有啥区别?【英文标题】:What are the differences between a multidimensional array and an array of arrays in C#?多维数组和 C# 中的数组数组有什么区别? 【发布时间】:2010-10-10 12:13:00 【问题描述】:

C# 中多维数组double[,] 和array-of-arrays double[][] 有什么区别?

如果有区别,每个人的最佳用途是什么?

【问题讨论】:

第一个double[,]是一个矩形数组,而double[][]被称为“锯齿状数组”。第一个将具有相同数量的每行“列”,而第二个将(可能)具有不同数量的每行“列”。 【参考方案1】:

数组数组(锯齿状数组)比多维数组更快,可以更有效地使用。多维数组有更好的语法。

如果您使用锯齿状和多维数组编写一些简单的代码,然后使用 IL 反汇编程序检查编译后的程序集,您会发现锯齿状(或一维)数组的存储和检索是简单的 IL 指令,而多维数组的操作相同数组是总是较慢的方法调用。

考虑以下方法:

static void SetElementAt(int[][] array, int i, int j, int value)

    array[i][j] = value;


static void SetElementAt(int[,] array, int i, int j, int value)

    array[i, j] = value;

他们的 IL 如下:

.method private hidebysig static void  SetElementAt(int32[][] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed

  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldelem.ref
  IL_0003:  ldarg.2
  IL_0004:  ldarg.3
  IL_0005:  stelem.i4
  IL_0006:  ret
 // end of method Program::SetElementAt

.method private hidebysig static void  SetElementAt(int32[0...,0...] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed

  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  ldarg.3
  IL_0004:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_0009:  ret
 // end of method Program::SetElementAt

使用交错数组时,您可以轻松执行行交换和行调整大小等操作。也许在某些情况下使用多维数组会更安全,但即使是 Microsoft FxCop 也告诉您在使用它来分析项目时应该使用锯齿状数组而不是多维数组。

【讨论】:

@John,自己衡量,不要做任何假设。 多维数组在逻辑上应该更有效,但 JIT 编译器的实现却不是。上面的代码没有用,因为它没有在循环中显示数组访问。 @Henk Holterman - 请参阅下面的答案,在 Windows 上,锯齿状数组可能很快,但必须意识到这完全是 CLR 特定的,而不是例如情况。单声道... 我知道这是一个老问题,只是想知道自从提出这个问题以来,CLR 是否已经针对多维数组进行了优化。 @arthur C# 编译器不做优化,JIT 做。查看 IL 不会告诉您它是如何优化的。【参考方案2】:

简单地说,多维数组类似于 DBMS 中的表。 Array of Array(锯齿状数组)让您可以让每个元素保存另一个相同类型的可变长度数组。

因此,如果您确定数据的结构看起来像一个表格(固定的行/列),您可以使用多维数组。锯齿数组是固定元素,每个元素可以容纳一个可变长度的数组

例如伪代码:

int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;

将以上内容视为 2x2 表格:

1 | 2
3 | 4
int[][] jagged = new int[3][]; 
jagged[0] = new int[4]   1,  2,  3,  4 ; 
jagged[1] = new int[2]  11, 12 ; 
jagged[2] = new int[3]  21, 22, 23 ; 

将上述视为每一行具有可变列数:

 1 |  2 |  3 | 4
11 | 12
21 | 22 | 23

【讨论】:

在决定使用什么时,这才是真正重要的......不是这个速度的东西......当你有一个方形阵列时,速度可能会成为一个因素。【参考方案3】:

多维数组创建了一个很好的线性内存布局,而锯齿状数组则意味着几个额外的间接级别。

在锯齿状数组 var jagged = new int[10][5] 中查找值 jagged[3][6] 的工作方式如下:查找索引 3 处的元素(这是一个数组)并查找该数组中索引 6 处的元素(这是一个值)。在这种情况下,对于每个维度,都有一个额外的查找(这是一种昂贵的内存访问模式)。

一个多维数组在内存中线性布局,实际值是通过将索引相乘来找到的。但是,给定数组 var mult = new int[10,30],该多维数组的 Length 属性返回元素的总数,即 10 * 30 = 300。

锯齿状数组的Rank 属性始终为1,但多维数组可以有任何秩。任何数组的GetLength方法都可以用来获取每个维度的长度。对于本例中的多维数组,mult.GetLength(1) 返回 30。

索引多维数组更快。例如给定此示例中的多维数组mult[1,7] = 30 * 1 + 7 = 37,获取该索引 37 处的元素。这是一种更好的内存访问模式,因为只涉及一个内存位置,即数组的基地址.

因此,多维数组分配一个连续的内存块,而锯齿状数组不必是正方形,例如jagged[1].Length 不必等于 jagged[2].Length,这对于任何多维数组都是正确的。

性能

在性能方面,多维数组应该更快。快得多,但由于 CLR 实现非常糟糕,它们并没有。

 23.084  16.634  15.215  15.489  14.407  13.691  14.695  14.398  14.551  14.252 
 25.782  27.484  25.711  20.844  19.607  20.349  25.861  26.214  19.677  20.171 
  5.050   5.085   6.412   5.225   5.100   5.751   6.650   5.222   6.770   5.305 

第一行是锯齿数组的时序,第二行是多维数组,第三行应该是这样。该程序如下所示,仅供参考,这是在运行单声道时测试的。 (windows 时间有很大不同,主要是由于 CLR 实现的变化)。

在 Windows 上,锯齿状数组的时序要好得多,这与我自己对多维数组查找的解释大致相同,请参见“Single()”。遗憾的是windows JIT-compiler实在是太笨了,不幸的是,这使得这些性能讨论变得困难,有太多的不一致。

这些是我在 windows 上得到的时间,这里也是一样的,第一行是锯齿状数组,第二行是多维数组,第三行是我自己实现的多维数组,请注意与单声道相比,这在 windows 上要慢多少。

  8.438   2.004   8.439   4.362   4.936   4.533   4.751   4.776   4.635   5.864
  7.414  13.196  11.940  11.832  11.675  11.811  11.812  12.964  11.885  11.751
 11.355  10.788  10.527  10.541  10.745  10.723  10.651  10.930  10.639  10.595

源代码:

using System;
using System.Diagnostics;
static class ArrayPref

    const string Format = "0,7:0.000 ";
    static void Main()
    
        Jagged();
        Multi();
        Single();
    

    static void Jagged()
    
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        
            var timer = new Stopwatch();
            timer.Start();
            var jagged = new int[dim][][];
            for(var i = 0; i < dim; i++)
            
                jagged[i] = new int[dim][];
                for(var j = 0; j < dim; j++)
                
                    jagged[i][j] = new int[dim];
                    for(var k = 0; k < dim; k++)
                    
                        jagged[i][j][k] = i * j * k;
                    
                
            
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        
        Console.WriteLine();
    

    static void Multi()
    
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        
            var timer = new Stopwatch();
            timer.Start();
            var multi = new int[dim,dim,dim];
            for(var i = 0; i < dim; i++)
            
                for(var j = 0; j < dim; j++)
                
                    for(var k = 0; k < dim; k++)
                    
                        multi[i,j,k] = i * j * k;
                    
                
            
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        
        Console.WriteLine();
    

    static void Single()
    
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        
            var timer = new Stopwatch();
            timer.Start();
            var single = new int[dim*dim*dim];
            for(var i = 0; i < dim; i++)
            
                for(var j = 0; j < dim; j++)
                
                    for(var k = 0; k < dim; k++)
                    
                        single[i*dim*dim+j*dim+k] = i * j * k;
                    
                
            
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        
        Console.WriteLine();
    

【讨论】:

尝试自己计时,看看两者的表现如何。锯齿状数组在 .NET 中得到了更多优化。它可能与边界检查有关,但无论原因如何,时序和基准都清楚地表明,锯齿状数组比多维数组访问速度更快。 但是您的时间似乎太小(几毫秒)。在这个级别,您将受到系统服务和/或驱动程序的很多干扰。让你的测试更大,至少需要一两秒钟。 @JohnLeidegren:半个世纪以来,多维数组在索引一维时比另一维更好地工作这一事实已被理解,因为仅在一个特定维度上不同的元素将连续存储在内存中,并且对于许多类型的记忆(过去和现在),访问连续项目比访问远距离项目更快。我认为在 .net 中,应该通过你正在做的最后一个下标来获得最佳结果索引,但是在任何情况下测试交换下标的时间都可能会提供信息。 @supercat:C# 中的多维数组存储在row-major order 中,交换下标的顺序会更慢,因为您将不连续地访问内存。顺便说一句,报告的时间不再准确,多维数组的速度几乎是锯齿数组的两倍(在最新的 .NET CLR 上测试),这应该是这样的...... 我知道这有点迂腐,但我不得不提一下,这不是 Windows vs Mono,而是 CLR vs Mono。你有时似乎混淆了这些。两者不等价; Mono 也适用于 Windows。【参考方案4】:

多维数组是 (n-1) 维矩阵。

所以int[,] square = new int[2,2] 是 2x2 方阵,int[,,] cube = new int [3,3,3] 是一个立方体 - 方阵 3x3。不需要比例。

锯齿状数组只是数组的数组 - 每个单元格都包含一个数组的数组。

所以MDA是成比例的,JD可能不是!每个单元格可以包含一个任意长度的数组!

【讨论】:

【参考方案5】:

前言:此评论是针对the answer provided by okutane,但由于SO的愚蠢信誉系统,我无法将其发布在它所属的地方。

由于方法调用而导致一个比另一个慢的断言是不正确的。由于更复杂的边界检查算法,一个比另一个慢。您可以通过查看而不是查看 IL,而是查看已编译的程序集来轻松验证这一点。例如,在我的 4.5 安装中,访问存储在 ecx 指向的二维数组中的元素(通过 edx 中的指针),索引存储在 eax 和 edx 中,如下所示:

sub eax,[ecx+10]
cmp eax,[ecx+08]
jae oops //jump to throw out of bounds exception
sub edx,[ecx+14]
cmp edx,[ecx+0C]
jae oops //jump to throw out of bounds exception
imul eax,[ecx+0C]
add eax,edx
lea edx,[ecx+eax*4+18]

在这里,您可以看到方法调用没有开销。由于非零索引的可能性,边界检查非常复杂,这是锯齿状数组不提供的功能。如果我们删除非零情况的 sub、cmp 和 jmps,代码几乎可以解析为 (x*y_max+y)*sizeof(ptr)+sizeof(array_header)。这种计算与随机访问元素的任何其他计算一样快(一个乘法可以用移位代替,因为这就是我们选择字节大小为两位的幂的全部原因)。

另一个复杂情况是,在许多情况下,现代编译器会在迭代一维数组时优化元素访问的嵌套边界检查。结果是基本上只是在数组的连续内存上推进一个索引指针的代码。多维数组的简单迭代通常涉及额外的嵌套逻辑层,因此编译器不太可能优化操作。因此,即使访问单个元素的边界检查开销在数组维度和大小方面分摊到恒定的运行时,一个简单的测试用例来衡量差异可能需要很多倍的时间来执行。

【讨论】:

感谢您纠正 okutane(不是 Dmitry)的答案。令人讨厌的是,人们在 *** 上给出错误答案并获得 250 次赞成票,而其他人给出正确答案却获得的票数少得多。但最后,IL 代码是无关紧要的。您必须真正衡量速度才能说出有关性能的任何信息。是你做的吗?我认为差异会很荒谬。【参考方案6】:

这可能在上面的答案中已经提到但没有明确地提到:对于锯齿状数组,您可以使用array[row] 来引用一整行数据,但这对于多维数组是不允许的。

【讨论】:

【参考方案7】:

我正在解析由 ildasm 生成的 .il 文件,以构建一个包含程序集、类、方法和存储过程的数据库,以用于进行转换。我遇到了以下问题,这破坏了我的解析。

.method private hidebysig instance uint32[0...,0...] 
        GenerateWorkingKey(uint8[] key,
                           bool forEncryption) cil managed

Serge Lidin 所著的 Expert .NET 2.0 IL Assembler 一书,Apress,2006 年出版,第 8 章,原始类型和签名,第 149-150 页解释了这一点。

&lt;type&gt;[] 被称为&lt;type&gt; 的向量,

&lt;type&gt;[&lt;bounds&gt; [&lt;bounds&gt;**] ] 被称为&lt;type&gt; 的数组

** 表示可以重复,[ ] 表示可选。

例子:让&lt;type&gt; = int32.

1) int32[...,...] 是一个未定义下界和大小的二维数组

2) int32[2...5] 是一个下界为 2、大小为 4 的一维数组。

3) int32[0...,0...] 是一个下界为 0 且大小未定义的二维数组。

汤姆

【讨论】:

【参考方案8】:

除了其他答案,请注意,多维数组被分配为堆上的一个大块对象。这有一些含义:

    一些多维数组将被分配到大对象堆 (LOH) 上,否则它们的等效锯齿状数组对应物将没有。 GC 将需要找到一个连续的空闲内存块来分配多维数组,而锯齿状数组可能能够填补由堆碎片引起的空白...这在 .NET 中通常不是问题因为压缩,但 LOH 默认情况下不会被压缩(您必须要求它,并且每次需要时都必须询问)。 如果您只使用锯齿状数组,则在出现问题之前,您需要查看&lt;gcAllowVeryLargeObjects&gt; 的多维数组方式

【讨论】:

【参考方案9】:

更新 .NET 6:

随着 .NET 6 的发布,我认为现在是重新审视这个话题的好时机。我为新的 .NET 重写了测试代码并运行它,要求每个部分至少运行一秒钟。基准测试是在 AMD Ryzen 5600x 上完成的。

结果?情况很复杂。对于较小和较大的阵列( ~200x200x200),单阵列似乎性能最高,而锯齿阵列在两者之间速度最快。不幸的是,从我的测试看来,多维是迄今为止最慢的选择。最好的性能是最快选项的两倍。但!这取决于您需要数组的用途,因为在 50^3 立方体上初始化锯齿状数组可能需要更长的时间,初始化时间大约是单维数组的 3 倍。多维只比单维慢一点。

结论?如果您需要快速代码,请在将要运行的机器上自行对其进行基准测试。 CPU架构可以完全改变每种方法的相对性能。

数字!

Method name         Ticks/Iteration     Scaled to the best
Array size 1x1x1 (10,000,000 iterations):
Jagged:             0.15                4.28
Single:             0.035               1
Multi-dimensional:  0.77                22

Array size 10x10x10 (25,000 iterations):
Jagged:             15                  1.67
Single:             9                   1
Multi-dimensional:  56                  6.2

Array size 25x25x25 (25,000 iterations):
Jagged:             157                 1.3
Single:             120                 1
Multi-dimensional:  667                 5.56

Array size 50x50x50 (10,000 iterations):
Jagged:             1,140               1
Single:             2,440               2.14
Multi-dimensional:  5,210               4.57

Array size 100x100x100 (10,000 iterations):
Jagged:             9,800               1
Single:             19,800              2
Multi-dimensional:  41,700              4.25

Array size 200x200x200 (1,000 iterations):
Jagged:             161,622             1
Single:             175,507             1.086
Multi-dimensional:  351,275             2.17

Array size 500x500x500 (100 iterations):
Jagged:             4,057.413           1.5
Single:             2,709,301           1
Multi-dimensional:  5,359,393           1.98

不相信我?自己运行并验证。

注意:恒定大小似乎为锯齿状数组提供了优势,但不足以改变我的基准测试中的顺序。在某些情况下,我测量到使用用户输入的大小来处理锯齿状数组时性能降低了约 7%,单个数组没有差异,而多维数组的差异非常小(约 1% 或更少)。在锯齿状阵列带头的中间最为突出。

    using System.Diagnostics;

const string Format = "0,7:0.000 ";
const int TotalPasses = 25000;
const int Size = 50;
Stopwatch timer = new();

var functionList = new List<Action>  Jagged, Single, SingleStandard, Multi ;

Console.WriteLine("0,51,202,203,204,20", "Run", "Ticks", "ms", "Ticks/Instance", "ms/Instance");

foreach (var item in functionList)

    var warmup = Test(item);
    var run = Test(item);

    Console.WriteLine($"item.Method.Name:");
    PrintResult("warmup", warmup);
    PrintResult("run", run);
    Console.WriteLine();


static void PrintResult(string name, long ticks)

    Console.WriteLine("0,101,202,203,204,20", name, ticks, string.Format(Format, (decimal)ticks / TimeSpan.TicksPerMillisecond), (decimal)ticks / TotalPasses, (decimal)ticks / TotalPasses / TimeSpan.TicksPerMillisecond);


long Test(Action func)

    timer.Restart();
    func();
    timer.Stop();
    return timer.ElapsedTicks;


static void Jagged()

    for (var passes = 0; passes < TotalPasses; passes++)
    
        var jagged = new int[Size][][];
        for (var i = 0; i < Size; i++)
        
            jagged[i] = new int[Size][];
            for (var j = 0; j < Size; j++)
            
                jagged[i][j] = new int[Size];
                for (var k = 0; k < Size; k++)
                
                    jagged[i][j][k] = i * j * k;
                
            
        
    


static void Multi()

    for (var passes = 0; passes < TotalPasses; passes++)
    
        var multi = new int[Size, Size, Size];
        for (var i = 0; i < Size; i++)
        
            for (var j = 0; j < Size; j++)
            
                for (var k = 0; k < Size; k++)
                
                    multi[i, j, k] = i * j * k;
                
            
        
    


static void Single()

    for (var passes = 0; passes < TotalPasses; passes++)
    
        var single = new int[Size * Size * Size];
        for (var i = 0; i < Size; i++)
        
            int iOffset = i * Size * Size;
            for (var j = 0; j < Size; j++)
            
                var jOffset = iOffset + j * Size;
                for (var k = 0; k < Size; k++)
                
                    single[jOffset + k] = i * j * k;
                
            
        
    


static void SingleStandard()

    for (var passes = 0; passes < TotalPasses; passes++)
    
        var single = new int[Size * Size * Size];
        for (var i = 0; i < Size; i++)
        
            for (var j = 0; j < Size; j++)
            
                for (var k = 0; k < Size; k++)
                
                    single[i * Size * Size + j * Size + k] = i * j * k;
                
            
        
    

经验教训:始终在基准测试中包含 CPU,因为它会有所作为。这次做了吗?我不知道,但我怀疑它可能已经。


原答案:

我想对此进行更新,因为在 .NET Core 中,多维数组比锯齿状数组更快。我从John Leidegren 运行了测试,这些是 .NET Core 2.0 预览版 2 上的结果。我增加了维度值以使来自后台应用程序的任何可能影响不那么明显。

Debug (code optimalization disabled)
Running jagged 
187.232 200.585 219.927 227.765 225.334 222.745 224.036 222.396 219.912 222.737 

Running multi-dimensional  
130.732 151.398 131.763 129.740 129.572 159.948 145.464 131.930 133.117 129.342 

Running single-dimensional  
 91.153 145.657 111.974  96.436 100.015  97.640  94.581 139.658 108.326  92.931 


Release (code optimalization enabled)
Running jagged 
108.503 95.409 128.187 121.877 119.295 118.201 102.321 116.393 125.499 116.459 

Running multi-dimensional 
 62.292  60.627  60.611  60.883  61.167  60.923  62.083  60.932  61.444  62.974 

Running single-dimensional 
 34.974  33.901  34.088  34.659  34.064  34.735  34.919  34.694  35.006  34.796 

我研究了反汇编,这就是我发现的

jagged[i][j][k] = i * j * k; 需要 34 条指令才能执行

multi[i, j, k] = i * j * k; 需要 11 条指令才能执行

single[i * dim * dim + j * dim + k] = i * j * k; 需要 23 条指令才能执行

我无法确定为什么一维数组仍然比多维数组快,但我的猜测是它与 CPU 上的一些优化有关

【讨论】:

【参考方案10】:

我想我会在未来加入 .NET 5 的一些性能结果,因为这将是每个人从现在开始使用的平台。

这些与John Leidegren 使用的测试相同(2009 年)。

我的结果(.NET 5.0.1):

  Debug:
  (Jagged)
  5.616   4.719   4.778   5.524   4.559   4.508   5.913   6.107   5.839   5.270
  
  (Multi)
  6.336   7.477   6.124   5.817   6.516   7.098   5.272   6.091  25.034   6.023
  
  (Single)
  4.688   3.494   4.425   6.176   4.472   4.347   4.976   4.754   3.591   4.403


  Release(code optimizations on):
  (Jagged)
  2.614   2.108   3.541   3.065   2.172   2.936   1.681   1.724   2.622   1.708

  (Multi)
  3.371   4.690   4.502   4.153   3.651   3.637   3.580   3.854   3.841   3.802

  (Single)
  1.934   2.102   2.246   2.061   1.941   1.900   2.172   2.103   1.911   1.911

在 6 核 3.7GHz AMD Ryzen 1600 机器上运行。

看起来性能比仍然大致相同。我想说,除非你真的很努力地优化,否则只需使用多维数组,因为语法更容易使用。

【讨论】:

【参考方案11】:

使用基于John Leidegren 的测试,我使用 .NET 4.7.2 对结果进行了基准测试,这是我的目的的相关版本,并认为我可以分享。我最初是从 dotnet core GitHub 存储库中的this comment 开始的。

随着阵列大小的变化,性能似乎有很大差异,至少在我的设置中,1 个处理器至强与 4 个物理 8 个逻辑。

w = 初始化一个数组,并将 int i * j 放入其中。 wr = do w,然后在另一个循环中将 int x 设置为 [i,j]

随着数组大小的增长,多维的表现似乎更胜一筹。

Size rw Method Mean Error StdDev Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
1800*500 w Jagged 2.445 ms 0.0959 ms 0.1405 ms 578.1250 281.2500 85.9375 3.46 MB
1800*500 w Multi 3.079 ms 0.2419 ms 0.3621 ms 269.5313 269.5313 269.5313 3.43 MB
2000*4000 w Jagged 50.29 ms 3.262 ms 4.882 ms 5937.5000 3375.0000 937.5000 30.62 MB
2000*4000 w Multi 26.34 ms 1.797 ms 2.690 ms 218.7500 218.7500 218.7500 30.52 MB
2000*4000 wr Jagged 55.30 ms 3.066 ms 4.589 ms 5937.5000 3375.0000 937.5000 30.62 MB
2000*4000 wr Multi 32.23 ms 2.798 ms 4.187 ms 285.7143 285.7143 285.7143 30.52 MB
1000*2000 wr Jagged 11.18 ms 0.5397 ms 0.8078 ms 1437.5000 578.1250 234.3750 7.69 MB
1000*2000 wr Multi 6.622 ms 0.3238 ms 0.4847 ms 210.9375 210.9375 210.9375 7.63 MB

更新:最后两个测试使用 double[,] 而不是 int[,]。考虑到错误,差异似乎很大。使用 int 时,jagged 与 md 的均值比率在 1.53x 和 1.86x 之间,使用 doubles 时,它是 1.88x 和 2.42x。

Size rw Method Mean Error StdDev Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
1000*2000 wr Jagged 26.83 ms 1.221 ms 1.790 ms 3062.5000 1531.2500 531.2500 15.31 MB
1000*2000 wr Multi 12.61 ms 1.018 ms 1.524 ms 156.2500 156.2500 156.2500 15.26 MB

【讨论】:

【参考方案12】:

锯齿状数组是数组或数组的数组,其中每一行都包含一个自己的数组。

这些数组的长度可以不同于其他行中的长度。

声明和分配数组数组

与常规多维数组相比,锯齿状数组的声明的唯一区别是我们不只有一对括号。对于锯齿状数组,我们每个维度都有一对括号。我们这样分配它们:

int[][] exampleJaggedArray;
jaggedArray = new int[2][];
jaggedArray[0] = new int[5];
jaggedArray[1] = new int[3];

初始化数组数组

int[][] exampleJaggedArray = 
new int[] 5, 7, 2,
new int[] 10, 20, 40,
new int[] 3, 25
;

内存分配

锯齿状数组是引用的聚合。锯齿状数组不直接包含任何数组,而是有指向它们的元素。大小是未知的,这就是为什么 CLR 只保留对内部数组的引用。在我们为交错数组的一个数组元素分配内存后,引用开始指向动态内存中新创建的块。

变量exampleJaggedArray存储在程序的执行栈中,指向动态内存中的一个块,其中包含对内存中其他三个块的三个引用的序列;它们中的每一个都包含一个整数数组——锯齿状数组的元素:

【讨论】:

以上是关于多维数组和 C# 中的数组数组有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

Java 和 C# 中的多维数组

有啥可以代替多维数组?

C# 中的多维关联数组

如何从 C# 中的嵌套循环写入多维数组?

为啥我们既有交错数组又有多维数组?

JS----使用归递获取多维数组中的某列值