概念
数组是一个存储雷同数据类型的固定大小的程序汇合,容许将多个数据项当作一个汇合来解决的数据结构。
数组的申明
//dataType[] arrayName
int[] intArray;
// 初始化
intArray = new int[3];
第一行代码申明了一个 int 类型的数组,此时并没有调配数组,所以默认赋值为 null。
第二行代码将会在托管堆上调配包容 3 个未装箱的 int 值的内存块,并默认为所有值赋值为 0。
援用类型数组
class People { }
People[] peopleArray;
// 全副初始化为 null,未调配值,相当于创立了一组援用
peopleArray = new People[3];
因为数组是援用类型,所以不论是值类型数组还是援用类型数组,都是存在于托管堆中的,值类型数组和援用类型数组在托管中的状况如图:
上图的 People 数组,就是执行了以下代码的后果
peopleArray[0] = new Man();
peopleArray[1] = new Woman();
peopleArray[2] = new Child();
数组类型
- 0 基数组 (索引从 0 开始)
- 多维数组 (包含一维数组)
- 交织数组 (由数组形成的数组)
申明多维数组
int[,] intArrays = new int[2, 2]; // 二维数组
string[,,] strArrays = new string[1, 2, 2]; // 三维数组
申明交织数组
int[][] interleavedArray = new int[3][];
interleavedArray[0] = new int[1];
interleavedArray[1] = new int[2];
interleavedArray[2] = new int[3];
将数组的申明与初始化合并
int[] intArray = new int[] {1, 2, 3};
int[,] intArrays = new int[,] {{ 1, 2} };// 二维
// 简化
var intArray = new int[] { 1, 2, 3};
// 持续简化
var intArray = new[] { 1, 2, 3};
* 在应用隐式类型的数组性能时 (不指定数组的具体类型),编译器会查看用于初始化数组元素的类型,并抉择所有元素最靠近的独特基类作为数组的类型,如果其中某一个元素与其它不具备雷同类型或基类型编译器将报错:找不到隐式类型数组的最佳类型。
隐式类型数组和匿名类型相结合
var peoples = new[] { new { Name = "A"}, new {Name = "B"} };
// 输入
//A
//B
foreach (var item in peoples)
Console.WriteLine(item.Name);
* 应用隐式类型数组需保障类型的同一性
数组协变性
数组协变性也称数组的转型,CLR 容许将援用类型的数组从一种类型隐式转换成另一种类型,数组转型须要具备以下条件:
- 两个数组类型必须维数雷同
- 源类型到指标类型必须存在一个显示或隐式的转换
*CLR 不容许将值类型元素的数组转型成其它任何类型
People[] peopleArray;
object[] objectArray = peopleArray; // 正确
int[,] intArrays = new int[,] {{ 1, 2} };
object[,] objectArray = intArrays; // 谬误
Array.Copy 变相实现值类型元素数组的转型
var intArray = new[] { 1, 2, 3};
object[] objectArray = new object[intArray.Length];
Array.Copy(intArray, objectArray, intArray.Length);
Array.Copy 办法能执行以下转换:
- 将值类型的元素装箱成为援用类型的元素
- 将援用类型的元素拆箱成为值类型的元素
* 因为 Copy 办法会产生装箱或者拆箱,毋庸置疑会对性能产生影响,应用 Array.ConstrainedCopy 办法将不会产生装箱拆箱操作,同样也有绝对的限度: 源数组类型元素与指标数组元素类型雷同或者派生自同一个基类。
数组的传递和返回倡议
数组是援用类型,所以数组作为参数传递给办法时,传递的其实是援用,所以在办法外部对数组进行操作的后果将会间接影响源数据。
办法在返回一个数组时,最好不要返回 null,即便这个数组是空数组,起因如下:
int[] intArray = new int[] { };
// 遍历空数组
foreach (var item in intArray)
Console.WriteLine(item);
int[] intArray = null;
// 遍历值为 null 的数组
if (intArray != null)
{foreach (var item in intArray)
Console.WriteLine(item);
}
应用 CreateInstance 创立上限非 0 的数组,该办法容许指定数组元素的类型,数组的维数,每一维的上限和每一维的元素数目
int[] lowerBounds = { 3, 1}; // 每一维数数组的上限
int[] lengths = { 3, 2}; // 每一维数数组的长度
Decimal[,] decimalArray = (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);
decimalArray[3, 1] = 1;
decimalArray[3, 2] = 2;
for (int i = decimalArray.GetLowerBound(0); i <= decimalArray.GetUpperBound(0); i++)
{for (int j = decimalArray.GetLowerBound(1); j <= decimalArray.GetUpperBound(1); j++)
{Console.WriteLine("i : {0}, j : {1}, value : {2}", i, j, decimalArray[i, j]);
}
}
输入:
创立一维的非 0 基数组
int[] myArrLen = { 4};
int[] myArrLow = { 2};
var myArrayTwo = Array.CreateInstance(typeof(int), myArrLen, myArrLow);
// 赋值
myArrayTwo.SetValue(1, 2);
myArrayTwo.SetValue(2, 3);
myArrayTwo.SetValue(3, 4);
myArrayTwo.SetValue(4, 5);
// 取值
myArrayTwo.GetValue(4);
* 必须应用 Array 的 SetValue 与 GetValue 办法拜访一维非 0 基数组的元素
拜访数组的性能
在遍历 0 基一维数组的时候,通常须要拜访数组的 Length,因为 JIT 编译器晓得 Length 是 Array 的属性,所以不会把它当作一个办法每次循环都调用,而会在第一次循环的时候调用一次,并将值存储到一个长期变量中,之后每次循环查看这个长期变量即可,从而晋升性能。
而拜访非 0 基一维数组或多维数组的时候,JIT 编译器不会将索引查看从循环中抽出来,导致每次循环都得验证指定的索引,从而影响代码的速度,这也是性能不如 0 基一维数组的起因。
应用交织数组和 unsafe 晋升数组拜访性能
// 多维数组
int[,] intArrays = new int[10000,10000];
var sw = Stopwatch.StartNew();
var sum = 0;
// 一般平安遍历
for (int i = 0; i < 10000; i++)
for (int j = 0; j < 10000; j++)
sum += intArrays[i, j];
Console.WriteLine("遍历二维数组 耗时: {0}", sw.Elapsed);
// 交织数组
int[][] interleavedArray = new int[10000][];
sw = Stopwatch.StartNew();
// 一般平安遍历
for (int i = 0; i < 10000; i++)
for (int j = 0; j < 10000; j++)
sum += intArrays[i, j];
Console.WriteLine("遍历交织数组 耗时: {0}", sw.Elapsed);
//unsafe 遍历
sw = Stopwatch.StartNew();
Test(intArrays);
Console.WriteLine("unsafe 遍历数组 耗时: {0}", sw.Elapsed);
private static unsafe void Test(int[,] arg)
{
var sum = 0;
fixed(Int32 * pi = arg)
{for (int i = 0; i < 10000; i++)
{
var temp = i * 10000;
for (int j = 0; j < 10000; j++)
sum += pi[temp + j];
}
}
}
测试后果能够得出:
遍历二维数组最慢,交织数组平安遍历工夫少于平安遍历二维数组的耗时,然而因为创立交织数组须要在堆上为每一维调配一个对象,造成垃圾回收,所以创立交织数组所破费的工夫要大于创立二维数组的工夫。
因而咱们能够在须要创立大量多维数组时,而不会频繁拜访数组中的元素,那么抉择创立多维数组性能较好。反之如果多维数组只需创立一次,并且须要频繁拜访数组中的元素,那么就是用交织数组性能来得更好一些。
显然不平安代码是性能最好的,然而应用该技术同样存在危险
- unsafe 间接操作内存,存在毁坏类型平安的危险
- 代码简单,不易写
- 应用 fixed,须要执行内存地址计算可读性升高
- 如果内存地址计算错误,会损坏内存数据,毁坏类型平安