在C#中提供了一维数组,多维数组,和交错数组(也叫齿状数组Jagged Array),由于C#本身并不直接支持非0基(0基的意思是数组的最小索引是0)的数组(虽然可以通过Array.CreateInstance()方法在C#中创建非0基数组),而且CLS(通用语言规范)也并不支持非0基数组,所以这篇文章中不讨论非0基数组。
1,一维0基数组:
一般情况下,建议大家最好使用一维0基数组,在C#直接支持的数组中,这种类型的数组性能最好,因为在IL中直接提供了特殊的指令来操作一维0基数组,大家看看如下的代码:
- int arr = new int;
- int i = arr;
这两行代码生成的IL是这样的:
- .entrypoint
- // 代码大小 13 (0xd)
- .maxstack 2
- .locals init ([0] int32 arr,
- int32 i)
- IL_0000: nop
- IL_0001: ldc.i4.5
- IL_0002: newarr [mscorlib]System.Int32
- IL_0007: stloc.0
- IL_0008: ldloc.0
- IL_0009: ldc.i4.3
- IL_000a: ldelem.i4
- IL_000b: stloc.1
- IL_000c: ret
大家可以看到IL提供了newarr指令来新建一个数组,ldelem.i4指令来获取Int32类型的数组元素,此外IL还6提供了其他的指令来操作一维0基数组:
ldelema :读取一维0基数组中一个元素的地址
ldlen :读取一维0基数组中的元素个数
stelem :为数组中的一个元素赋值
ldelem.ref:则用于操作所有包含引用类型对象的数组
2,交错数组
交错数组实际上就是数组的数组,也就是说交错数组中的每个元素本身又是一个数组,每个嵌套数组并不要求长度一致,大家看如下代码:
- int arr = new int;
- arr[0] = new int;
- arr = new int;
- arr = new int;
- arr = new int;
- arr = new int;
- int i=arr;
生成的IL代码如下:
- .entrypoint
- // 代码大小 60 (0x3c)
- .maxstack 3
- .locals init ([0] int32 arr,
- int32 i)
- IL_0000: nop
- IL_0001: ldc.i4.5
- IL_0002: newarr int32
- IL_0007: stloc.0
- IL_0008: ldloc.0
- IL_0009: ldc.i4.0
- IL_000a: ldc.i4.3
- IL_000b: newarr [mscorlib]System.Int32
- IL_0010: stelem.ref
- IL_0011: ldloc.0
- IL_0012: ldc.i4.1
- IL_0013: ldc.i4.4
- IL_0014: newarr [mscorlib]System.Int32
- IL_0019: stelem.ref
- IL_001a: ldloc.0
- IL_001b: ldc.i4.2
- IL_001c: ldc.i4.5
- IL_001d: newarr [mscorlib]System.Int32
- IL_0022: stelem.ref
- IL_0023: ldloc.0
- IL_0024: ldc.i4.3
- IL_0025: ldc.i4.6
- IL_0026: newarr [mscorlib]System.Int32
- IL_002b: stelem.ref
- IL_002c: ldloc.0
- IL_002d: ldc.i4.4
- IL_002e: ldc.i4.7
- IL_002f: newarr [mscorlib]System.Int32
- IL_0034: stelem.ref
- IL_0035: ldloc.0
- IL_0036: ldc.i4.3
- IL_0037: ldelem.ref
- IL_0038: ldc.i4.4
- IL_0039: ldelem.i4
- IL_003a: stloc.1
- IL_003b: ret
代码比较长,前面基本都是初始化的代码,大家不用太关心,但是大家明显能看到一维0基的交错数组同样也可以利用IL为一维0基数组提供的特殊指令,所以性能和一维0基数组相同,唯一的不同是多了一次嵌套数组的访问!
3,多维数组:
多维数组一般大家可能直观上的认为多维数组的性能也应该不错,因为而多维数组当中,由于用于存储元素的内存空间是连续的,而且数组的每一维元素个数固定,所以可以轻易的根据元素各维的索引值计算出元素在数组内存中偏移量,比如,一个多维数组arr[6,7],元素arr[2,4]的偏移量是2*7+4,事实证明这种直观看法并不正确,我们看如下代码:
- int[,] arr = new int[5,6];
- int i = arr[3, 4];
生成的IL代码如下:
- .entrypoint
- // 代码大小 19 (0x13)
- .maxstack 3
- .locals init ([0] int32[0,0] arr,
- int32 i)
- IL_0000: nop
- IL_0001: ldc.i4.5
- IL_0002: ldc.i4.6
- IL_0003: newobj instance void int32[0,0]::.ctor(int32,
- int32)
- IL_0008: stloc.0
- IL_0009: ldloc.0
- IL_000a: ldc.i4.3
- IL_000b: ldc.i4.4
- IL_000c: call instance int32 int32[0,0]::Get(int32,
- int32)
- IL_0011: stloc.1
- IL_0012: ret
大家都知道,数组是继承于Array的引用类型,这里int32[0,0]类型就是CLR为多维数组构造的一个类型,Get(int32,int32)方法是CLR为这个类型构造的一个方法,类似的方法还有:
- int Get(int d1, int d2); //获取特定元素的值
- void Set(int d1, int d2, int v); //设置特定元素的值
- int* Address(int d1, int d2); //获取特定元素的地址
函数调用本身是比较耗时的,因为它包含了参数的压栈出栈,以及程序控制流的转移等,显然,多维数组的这种通过函数调用来访问元素的方式没有一维0基的交错数组直接使用IL指令的方式快!