数组

代码幽篁使
• 阅读 1175
概念

数组是一个存储相同数据类型的固定大小的顺序集合,允许将多个数据项当作一个集合来处理的数据结构。

数组的声明
//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,需要执行内存地址计算可读性降低
  • 如果内存地址计算错误,会损坏内存数据,破坏类型安全
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
Java中Map,List与Set的区别
首先,数组和集合的区别:数组是大小固定的集合可以存储和操作数目不固定的一组数据,集合只能存放引用类型的的数据,不能存放基本数据类型特性List允许重复有序继承自ConnectionSet不允许重复无序继承自Connection
Wesley13 Wesley13
3年前
Java开发者容易犯的十个错误
!(https://oscimg.oschina.net/oscnet/c9f00cc918684fbe8a865119d104090b.gif)Top1.数组转换为数组列表将数组转换为数组列表,开发者经常会这样做:\java\List<StringlistArrays.asList(arr);Arr
Stella981 Stella981
3年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
3年前
HashMap 的底层实现原理
HashMap是一个用于存储KeyValue键值对的集合,每一个键值对也叫做Entry。这些个Entry分散存储在一个数组当中,这个数组就是HashMap的主干。HashMap数组每一个元素的初始值都是Null。 !(https://oscimg.oschina.net/oscnet/8495d30fe00a2865dd74088d2
Wesley13 Wesley13
3年前
ES6 新增的数组的方法
给定一个数组letlist\//wu:武力zhi:智力{id:1,name:'张飞',wu:97,zhi:10},{id:2,name:'诸葛亮',wu:55,zhi:99},{id:3,name:'赵云',wu:97,zhi:66},{id:4,na
Wesley13 Wesley13
3年前
.Net转Java自学之路—基础巩固篇十三(集合)
集合:集合是用于存储对象的一个工具。  集合与数组的特点    相同点:都是一个容器    不同点:      集合:可以存储对象,只能存储对象,集合长度可变。      数组:可以存储对象,也可以存储基本数据类型,数组长度固定。  容器对象有很多种,通过内部的数据结构来区分,数据结构就是一种数据存储方式。  在不断
达里尔 达里尔
1年前
给数组添加新数据,判断数据是否重复
多选要进行数组拼接,希望判断往原数组里添的新数据是否重复,封装个简易方法languageconstdataArrayname:'aaa',id:1,name:'bbb',id:2;constnewDataname:'ccc',id:2;//要添加的新数
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(