1. 数组大局观
数组是一个引用类型,也就是意味着数组的内存分配在托管堆上,并且我们在栈上维护的是他的指针而并非真正的数组。接下来我们分析下数组的元素,其中的元素无外乎是引用类型和值类型。当数组中的元素是值类型时,不同于int i;这样的代码。数组会根据数组的大小自动把元素的值初始化为他的默认值。例如:
static void Main(string[] args)
{
int[] intArray = new int[3];
foreach(int i in intArray)
{
Console.WriteLine(i);
}
DateTime[] dtArray = new DateTime[3];
foreach (DateTime i in dtArray)
{
Console.WriteLine(i);
}
}
结果如下:
当数组中的元素是引用类型时,实际上数组中的元素是一个指向对象实际内存空间的指针,占用4Bytes的空间。
2. 谈谈零基数组
从学C语言时起,相信老师就会对我们讲,数组的第一个索引是0,而不是1。但是在C#中,我们可以去构造一个非零基数组,在这一节,我们就来把这个说透。
在常规意义上,我们初始化一个数组,都默认是零基数组,这也使得数组成为了字符串后再一个初始化时特殊的类型。正如我们知道的一样,初始化一个字符串时,对应的IL指令是newstr,同样,初始化一个零基数组对应的IL指令是newarr。
当我们希望构造一个非零基数组时,我们可以以下的语句来做到:
static void Main(string[] args)
{
Array intArr = Array.CreateInstance(typeof(Int32), new int[] { 5 }, new int[] { 1 });
Console.WriteLine(intArr.GetValue(1).ToString());
Console.WriteLine(intArr.GetValue(0).ToString());
}
得到的测试结果便如下:
于是便证明,我们初始化了一个非零基数组。此外,延伸一下,我们还应该通过这个记住以下两个方法:
static void Main(string[] args)
{
Array intArr = Array.CreateInstance(typeof(Int32), new int[] { 5 }, new int[] { 1 });
Console.WriteLine(intArr.GetLowerBound(0));
Console.WriteLine(intArr.GetUpperBound(0));
}
得到的测试结果如下:
3. 谈谈效率问题
相信会有好多阴谋论者说,C#是个类型安全的语言,也就是意味着我循环时每次访问一次数组的元素,那么就要检查一次该索引是否会造成数组越界,于是就造成了一定的性能损失。那么在这里,我们就把这个问题说透。
我们在这里把数组分成零基数组,非零基数组,多维数组,交错数组四种情况来分别讨论这个问题。
零基数组是.NET中提倡使用的类型,并且初始化时提供了特殊的IL指令newarr则充分说明了他在.NET中的特殊性,自然.NET Framework也会为其提供很大的优化待遇。在循环访问数组时,如这样的代码:
static void Main(string[] args)
{
int[] intArr = new int[5];
for (int i = 0; i < 4; i++)
{
//Some Method
}
}
JIT编译器只会在循环开始之前检查一次4和intArr.GetUpperBound的大小关系,之后便不会对其进行干预。也就是说JIT编译器只对其检查一次安全,因此带来的性能损失是非常小的。
而对于非零基数组,我们来比较这样两段代码:
static void Main(string[] args)
{
Array intArr = Array.CreateInstance(typeof(Int32), new int[] { 5 }, new int[] { 1 });
Console.WriteLine(intArr.GetValue(1).ToString());
Console.WriteLine(intArr.Length);
//
int[] intArr1 = new int[5];
Console.WriteLine(intArr1[1]);
Console.WriteLine(intArr1.Length);
}
其实两者创建的几乎是相同的数组,调用的也几乎是一样的方法,但是我们看下IL却会发现两者有着惊人的不同,首先是非零基数组的IL:
接下来是零基数组的:
我们可以发现,对于非零基数组中的大部分操作,.NET Framework都提供了对应的IL指令,我们也可以理解为.NET Framework为其提供了特殊的优化。
当然,实际上,正如CLR via C#所说的一样:.NET Framework对应非零基数组没有任何方面的优化,每次访问都需要检查其上限和下限与索引之间的关系。效率的损耗是必然的。
事实上,当我们测试这样一段代码时,也会发现其实零基数组和非零基数组的区别是很大的:
static void Main(string[] args) { Array intArr = Array.CreateInstance(typeof(Int32), new int[] { 5 }, new int[] { 1 }); Console.WriteLine("intArr的?类à型í是?:o{0}", intArr.GetType()); // int[] intArr1 = new int[5]; Console.WriteLine("intArr1的?类à型í是?:o{0}", intArr1.GetType()); }
得到的结果如下:
接下来我们再来简单地说下多维数组和交错数组。
多维数组和非零基数组一样,都没有受到.NET Framework的特殊优待。
而交错数组,其实就是数组中的数组,因此效率实际上取决于数组中的数组是零基数组还是非零基数组。
那接下来的一节,我们来具体探讨一下交错数组和多维数组的区别和应用。
4. 多维数组和交错数组
考虑到两个词的翻译问题,在这里给出两个词的英文:
多维数组:Multi-dimensional Array。
交错数组:Jagged Array。
好,下面步入正题。
首先从二者的内存分布说起。
多维数组是一个整体的数组,因此他在内存中占据一个整体的托管堆内存块。
而交错数组实际上是数组中的数组,因此我们用二维交错数组来举例,其内存如图所示:
也就是说,如果是一个3*100的数组,也就是说需要初始化101次数组,当数组的元素更加多的时候,那创建和垃圾回收将带来巨大的效率损失。
因此,也就是说:交错数组的效率瓶颈在于创建和销毁上,而并非类型安全检查上。
于是,我们就可以得出这样的结论:
当一次创建,多次访问时,我们应该创建交错数组。
当一次创建,一次访问时,我们应该创建多维数组。
5. 用代码改善效率
上面说到了,访问非零基数组和多维数组的效率是比较低的,对于非零基数组,我们的应用比较少,但是多维数组,相信每个人都或多或少有着一定的应用,那么面对其性能问题,我们该怎么办呢?
我们先来想想,多维数组的访问,性能瓶颈在安全检查上。在C语言中,为什么没有这样的问题,对,因为C语言不会做这样的检查。于是,相信聪明的大家都会想到不安全代码。
改善多维数组以及非零基数组的效率问题,我们就用不安全代码。
static unsafe void Main(string[] args) { int[,] intArr = new int[3, 3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { intArr[i, j] = i * 3 + j; } } fixed (int* p = &intArr[0, 0]) { for (int i = 0; i < 3; i++) { int baseOffset = i * 3; for (int j = 0; j < 3; j++) { Console.WriteLine(baseOffset + j); } } } }
这里,我们又见到了C语言中熟悉的指针,相信不需要多加介绍了。这里唯一需要注意的就是fixed,由于在垃圾回收时采用的是代机制+压缩机制,因此其内存地址很可能发生改变,因此我们应该讲数组的内存地址锁住,防止我们访问到其他的内存地址而造成我们读取数据的错误。
6. 对零基数组的精益求精
当然,即使是零基数组,我们依然在托管堆上为其分配了内存空间。如果对性能要求极高,我们知道创建一个对象也是有着一定的时间损耗,其中包括分配内存空间,同步块索引,以及指向下一块内存空间的指针等一系列复杂的操作。那么我们就放弃掉托管堆这个东东,而直接在栈中来创建这个数组,这样又省去了很多时间,从而达到了和C语言相同的效果,代码如下:
static unsafe void Main(string[] args) { int* intArr=stackalloc int[10]; for (int i = 0; i < 10; i++) { intArr[i] = i; } for (int i = 0; i < 10; i++) { Console.WriteLine(intArr[i]); } }
这样,效率就进一步提高了,对于二维数组,我们一样可以如此创建。其代码与C语言完全等同。我在这里就不继续演示了。
7. 总结
在全文中,主要是对数组的各个方面做一个比较简略地介绍,其中包括数组的基础知识,分类,以及效率性能问题,最后就是用不安全代码来访问创建数组来提高性能。
不过最后说一句,在实际工作中,如果对性能没有特别高的要求,则没必要用不安全代码来操作数组,因为其很可能因为你的一些失误而带来其他的一些安全问题,并且对代码的可读性也是个比较大的伤害,这就有些得不偿失了。
发表评论
-
(转)ASP.NET文件上传控件——WebbUpload我下载了这个组件后做了一些修改并应用了ajax技术
2010-12-06 16:28 979我下载了这个组件后做了一些修改并应用了ajax技术,你们可以去 ... -
ASP.NET 下载管理的基础篇
2010-10-13 11:20 837很多时候一个网站是需要对文件下载进行管理的,不是任何人都允许下 ... -
C#实现获取CPU使用率的方法
2010-10-13 11:09 2129无需多说,直接上代码 using System; ... -
用C#取得远程IP地址,MAC地址的方法
2010-10-13 11:05 1290经常需要获得远程的地址,需要用sendarp这个函数来实现。具 ... -
C#在应用程序中实现自动升级(转)
2010-10-11 12:06 5895这是本人第一次写比较复杂的文章,表达不清之处,请各位见谅。好, ... -
.Net垃圾收集机制 了解算法与代龄
2010-10-08 17:40 634垃圾收集器在本质上就是负责跟踪所有对象被引用到的地方,关注 ... -
Asp.Net如何实现断点续传
2010-10-08 17:07 1069断点续传的原理 ... -
编码实现动态调用WebService的方法
2010-10-08 11:00 1688调用方法,同时也支持带ref参数的 /// ... -
ASP.NET得到当前代码位置的类名和方法名
2010-10-08 10:31 966protected void writeerror(obje ... -
文本字符的html格式转换
2010-10-08 10:23 762前一段仿泡泡网做了个论坛,遇到了个文本符号转化成相应htm ... -
C#串口serialPort操作
2010-10-08 10:20 1610现在大多数硬件设备均采用串口技术与计算机相连,因此串口的应 ... -
C#.Net创建不规则窗体的几种方法
2010-09-30 13:41 1528现在,C#创建不规则窗体不是一件难事,下面总结一下: 一、自 ... -
浅解XML与DataSet对象的关系及转换
2010-09-30 13:39 968在.NET Framework 中,经常使用XML 作为存储和 ... -
用Visual Studio来自动化测试
2010-09-30 13:32 1103自动化测试的实现 编写自动化测试也许对很多测试人员来说比较陌 ... -
10个C#编程和Visual Studio使用技巧
2010-09-30 13:29 779C#是一门伟大的编程语言,与C++和Java相比,它的语法更简 ...
相关推荐
相控阵天线讲的深入浅出,很有参考价值 1 Radiation 1 1.1 The Early History of Electricity and Magnetism 1 1.2 James Clerk Maxwell, The Union of Electricity and Magnetism 8 1.3 Radiation by Accelerated ...
变参array of const深入研究.mht
Array Leet Code solved samples in Java
1.1 Array Background 1 1.2 Systems Factors 2 1.3 Annotated Reference Sources 3 1.3.1 Adaptive Antenna Reference Books 5 References 5 2 Basic Array Characteristics 7 2.1 Uniformly Excited Linear Arrays...
打开您下载 ArrayConvert.exe 文件文件夹,然后双击 ArrayConvert.exe 文件。 在 解压缩到文件夹 框中键入 C:\ArrayConvert,然后单击 解压缩。 单击 确定,然后单击 关闭。 单击 开始,单击 运行,键入 regsvr32 C:...
delphi中String,PChar,PByte,Array of Char,Array of Byte 之间的互相转换
dojo的Array处理
一、Array APV系列设备功能简介 4 1.1. 服务器负载均衡(Server Load Balance) 4 1.1.1 SLB的工作模式 4 1.1.2. SLB的负载均衡算法 7 1.1.3. SLB的负载均衡策略 9 1.1.4 Array的SLB健康检查 10 1.1.5 Array的SLB的...
This Unit implements a base class for resizeable array types and a few specific derivatives for the common numeric types, as well as prototypes for array of pointers, PChars, and strings
Array.zip
a program to sum array elements
该资源主要介绍了android数组资源string-array integer-array的用法 包括int类型、string类型、文件类型的使用
试编写一个程序,要求比较数组ARRAY中的三个16位补码数,并根据比较结果在终端上显示如下信息: 如果三个数都不相等则显示0; 如果三个数有两个相等则显示1; 如果三个数都相等则显示2。
可通过 Array Configuration Utility 执行多个任务,诸如初始配置阵列控制器、将磁盘添加至现有配置或者重新配置阵列控制器。 支持Microsoft Windows Server 2003 x64 Edition/Microsoft Windows Server 2008 x64/...
用于说明init_array编写和调试的教程
这里,我通过一个简单的例子,并使用图形方式来了解数组。 $a = array(3 => 'a', 1 => 'b', 2 => 'c'); echo var_dump($a); [注]:使用箭头描述数组$a各个单元对应某一内存地址的数据值(实际上,它内部结构...
数组的概念 数组的申明和使用 Array类 ArrayList 泛型的使用 泛型的特征 泛型类、接口、方法的编写和使用 集合接口 枚举
Array.Copy 方法,介绍Array.copy的几种用法
Keil Array Visualization是一款功能强大的keil调试辅助工具。连接keil软件,然后读取变量或内存地址,然后用波形显示,可以选择多种不同的数据类型,选择大小端,导出为二进制文件,还可以导出到wav文件。
ARRAY2600手册