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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/597720/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 09:44:07  来源:igfitidea点击:

What are the differences between a multidimensional array and an array of arrays in C#?

c#multidimensional-arrayjagged-arrays

提问by ecleel

What are the differences between multidimensional arrays double[,]and array-of-arrays double[][]in C#?

C#中的多维数组double[,]和数组数组有什么区别double[][]

If there is a difference, what is the best use for each one?

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

采纳答案by okutane

Array of arrays (jagged arrays) are faster than multi-dimensional arrays and can be used more effectively. Multidimensional arrays have nicer syntax.

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

If you write some simple code using jagged and multidimensional arrays and then inspect the compiled assembly with an IL disassembler you will see that the storage and retrieval from jagged (or single dimensional) arrays are simple IL instructions while the same operations for multidimensional arrays are method invocations which are always slower.

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

Consider the following methods:

考虑以下方法:

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;
}

Their IL will be the following:

他们的 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

When using jagged arrays you can easily perform such operations as row swap and row resize. Maybe in some cases usage of multidimensional arrays will be more safe, but even Microsoft FxCop tells that jagged arrays should be used instead of multidimensional when you use it to analyse your projects.

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

回答by shahkalpesh

Simply put multidimensional arrays are similar to a table in DBMS.
Array of Array (jagged array) lets you have each element hold another array of the same type of variable length.

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

So, if you are sure that the structure of data looks like a table (fixed rows/columns), you can use a multi-dimensional array. Jagged array are fixed elements & each element can hold an array of variable length

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

E.g. Psuedocode:

例如伪代码:

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

Think of the above as a 2x2 table:

把上面的想象成一个 2x2 的表:

1 | 2
3 | 4
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 }; 

Think of the above as each row having variable number of columns:

将上面的内容视为每行具有可变列数:

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

回答by John Leidegren

A multidimensional array creates a nice linear memory layout while a jagged array implies several extra levels of indirection.

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

Looking up the value jagged[3][6]in a jagged array var jagged = new int[10][5]works like this: Look up the element at index 3 (which is an array) and look up the element at index 6 in that array (which is a value). For each dimension in this case, there's an additional look up (this is an expensive memory access pattern).

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

A multidimensional array is laid out linearly in memory, the actual value is found by multiplying together the indexes. However, given the array var mult = new int[10,30], the Lengthproperty of that multidimensional array returns the total number of elements i.e. 10 * 30 = 300.

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

The Rankproperty of a jagged array is always 1, but a multidimensional array can have any rank. The GetLengthmethod of any array can be used to get the length of each dimension. For the multidimensional array in this example mult.GetLength(1)returns 30.

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

Indexing the multidimensional array is faster. e.g. given the multidimensional array in this example mult[1,7]= 30 * 1 + 7 = 37, get the element at that index 37. This is a better memory access pattern because only one memory location is involved, which is the base address of the array.

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

A multidimensional array therefore allocates a continuous memory block, while a jagged array does not have to be square, e.g. jagged[1].Lengthdoes not have to equal jagged[2].Length, which would be true for any multidimensional array.

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

Performance

表现

Performance wise, multidimensional arrays should be faster. A lot faster, but due to a really bad CLR implementation they are not.

性能方面,多维数组应该更快。快了很多,但由于 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 

The first row are timings of jagged arrays, the second shows multidimensional arrays and the third, well that's how it should be. The program is shown below, FYI this was tested running mono. (The windows timings are vastly different, mostly due to the CLR implementation variations).

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

On windows, the timings of the jagged arrays are greatly superior, about the same as my own interpretation of what multidimensional array look up should be like, see 'Single()'. Sadly the windows JIT-compiler is really stupid, and this unfortunately makes these performance discussions difficult, there are too many inconsistencies.

在 Windows 上,锯齿状数组的时序要好得多,与我自己对多维数组查找方式的解释大致相同,请参阅“Single()”。可悲的是,windows JIT 编译器真的很愚蠢,不幸的是,这使得这些性能讨论变得困难,不一致之处太多了。

These are the timings I got on windows, same deal here, the first row are jagged arrays, second multidimensional and third my own implementation of multidimensional, note how much slower this is on windows compared to mono.

这些是我在 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

Source code:

源代码:

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();
    }
}

回答by abatishchev

Multi-dimension arrays are (n-1)-dimension matrices.

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

So int[,] square = new int[2,2]is square matrix 2x2, int[,,] cube = new int [3,3,3]is a cube - square matrix 3x3. Proportionality is not required.

int[,] square = new int[2,2]方阵 2x2int[,,] cube = new int [3,3,3]也是如此,是立方体 - 方阵 3x3。不要求比例。

Jagged arrays are just array of arrays - an array where each cell contains an array.

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

So MDA are proportional, JD may be not! Each cell can contains an array of arbitrary length!

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

回答by Eglin

Preface:This comment is intended to address the answer provided by okutane, but because of SO's silly reputation system, I can not post it where it belongs.

前言:此评论旨在解决okutane 提供的答案,但由于 SO 愚蠢的声誉系统,我无法将其张贴在它所属的位置。

Your assertion that one is slower than the other because of the method calls isn't correct. One is slower than the other because of more complicated bounds-checking algorithms. You can easily verify this by looking, not at the IL, but at the compiled assembly. For example, on my 4.5 install, accessing an element (via pointer in edx) stored in a two-dimensional array pointed to by ecx with indexes stored in eax and edx looks like 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]

Here, you can see that there's no overhead from method calls. The bounds checking is just very convoluted thanks to the possibility of non-zero indexes, which is a functionality not on offer with jagged arrays. If we remove the sub,cmp,and jmps for the non-zero cases, the code pretty much resolves to (x*y_max+y)*sizeof(ptr)+sizeof(array_header). This calculation is about as fast (one multiply could be replaced by a shift, since that's the whole reason we choose bytes to be sized as powers of two bits) as anything else for random access to an element.

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

Another complication is that there are plenty of cases where a modern compiler will optimize away the nested bounds-checking for element access while iterating over a single-dimension array. The result is code that basically just advances an index pointer over the contiguous memory of the array. Naive iteration over multi-dimensional arrays generally involves an extra layer of nested logic, so a compiler is less likely to optimize the operation. So, even though the bounds-checking overhead of accessing a single element amortizes out to constant runtime with respect to array dimensions and sizes, a simple test-case to measure the difference may take many times longer to execute.

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

回答by lznt

This might have been mentioned in the above answers but not explicitly: with jagged array you can use array[row]to refer a whole row of data, but this is not allowed for multi-d arrays.

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

回答by Thomas C Hutchinson

I am parsing .il files generated by ildasm to build a database of assemnblies, classes, methods, and stored procedures for use doing a conversion. I came across the following, which broke my parsing.

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

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

The book Expert .NET 2.0 IL Assembler, by Serge Lidin, Apress, published 2006, Chapter 8, Primitive Types and Signatures, pp. 149-150 explains.

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

<type>[]is termed a Vector of <type>,

<type>[]被称为 的向量<type>

<type>[<bounds> [<bounds>**] ]is termed an array of <type>

<type>[<bounds> [<bounds>**] ]被称为一个数组 <type>

**means may be repeated, [ ]means optional.

**手段可以重复,[ ]手段可选。

Examples: Let <type> = int32.

示例:让<type> = int32.

1) int32[...,...]is a two-dimensional array of undefined lower bounds and sizes

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

2) int32[2...5]is a one-dimensional array of lower bound 2 and size 4.

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

3) int32[0...,0...]is a two-dimensional array of lower bounds 0 and undefined size.

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

Tom

汤姆

回答by Joe Amenta

In addition to the other answers, note that a multidimensional array is allocated as one big chunky object on the heap. This has some implications:

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

  1. Some multidimensional arrays will get allocated on the Large Object Heap (LOH) where their equivalent jagged array counterparts would otherwise not have.
  2. The GC will need to find a single contiguous free block of memory to allocate a multidimensional array, whereas a jagged array might be able to fill in gaps caused by heap fragmentation... this isn't usually an issue in .NET because of compaction, but the LOH doesn't get compacted by default (you have to ask for it, and you have to ask every time you want it).
  3. You'll want to look into <gcAllowVeryLargeObjects>for multidimensional arrays waybefore the issue will ever come up if you only ever use jagged arrays.
  1. 一些多维数组将在大对象堆 (LOH) 上分配,否则它们的等效锯齿状数组对应物将不会有。
  2. GC 将需要找到一个连续的空闲内存块来分配多维数组,而锯齿状数组可能能够填充由堆碎片引起的间隙……由于压缩,这在 .NET 中通常不是问题,但默认情况下 LOH 不会被压缩(您必须要求它,并且每次需要时都必须询问)。
  3. 你会想看看<gcAllowVeryLargeObjects>多维数组的方式的问题都不会拿出如果你只曾经使用交错数组之前。

回答by adsamcik

I would like to update on this, because in .NET Core multi-dimensional arrays are faster than jagged arrays. I ran the tests from John Leidegrenand these are the results on .NET Core 2.0 preview 2. I increased the dimension value to make any possible influences from background apps less visible.

我想对此进行更新,因为在.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 

I looked into disassemblies and this is what I found

我调查了拆解,这就是我发现的

jagged[i][j][k] = i * j * k;needed 34 instructions to execute

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

multi[i, j, k] = i * j * k;needed 11 instructions to execute

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

single[i * dim * dim + j * dim + k] = i * j * k;needed 23 instructions to execute

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

I wasn't able to identify why single-dimensional arrays were still faster than multi-dimensional but my guess is that it has to do with some optimalization made on the CPU

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