CLR笔记:13.数组

CLR支持一维/多维/交错数组。
两种声明方式:

            Array a;
            a 
= new String[01];

            String[] s 
= new String[5];

注意,声明不要给与数组长度,因为此时还不分配内存;new时要指定长度。
        将数组声明为Array和像String[]这样带中括号的,效果是一样的,只是前者更灵活,当然类型不安全,可能会有运行期错误。

所有数组都隐式派生于System.Array,后者派生自System.Object,所以数组是引用类型。
    值类型的数组,new时,会创建实际的对象,所有元素都初始化为0;
    引用类型的数组,new时,只是创建引用,不会创建实际的对象。

CLS要求数组必须是0基数组。

每个数组都关联了一些额外的信息,称为开销信息(Overhead)。
JITer只在执行一次循环之前检查一次数组边界,而不是每次循环都这么做。

13.1    数组的类型转换
    对于引用类型的数组,两个数组类型必须维数相同,并且从源元素类型到目标元素类型,必须存在一个隐式(向父类转)或显示(向子类转)的类型转换。
    对于值类型的数组,不能将其转换为其他任何一种类型——使用Array.Copy方法替代:

            Int32[] ildim = new Int32[5];
            Object[] obldim 
= new Object[ildim.Length];
            Array.Copy(ildim, obldim, ildim.Length);

Array.Copy还可以再复制每个数组元素时进行必要的类型转换:
    *将值类型的元素装箱为引用类型的元素
    *将引用类型的元素拆箱为值类型的元素
    *加宽CLR基元值类型,Int32到Double
    *如果两个数组的类型不兼容(从Object[]转型为IFormattable[]),就对元素进行向下类型转换。

数组的协变性:将数组从一种类型转换为另一种类型,——会有性能损失,如下代码,会在运行期报错:

            String[] sa = new String[100];
            Object[] oa 
= sa;

            oa[
5= "Jax";
            oa[
3= 1;  //运行期错误

如果只是需要将数组中的某些元素复制到另一个数组,可以使用System.Buffer的BlockCopy()方法,执行速度比Array.Copy快,但是只支持基元类型,不具有转型能力。


13.3    所有数组都隐式实现IEnumerable,ICollection,IList
对于泛型接口,因为多维数组和非0基数组的问题,System.Array并不完全实现。
只有一维0基的引用类型的数组,在创建时,会自动实现了IEnumerable<T>,ICollection<T>,IList<T>,T为这个数组的类型;还为T的所有基类型B实现了IEnumerable<B>,ICollection<B>,IList<B>接口。
如FileStream[] fsArray,会自动实现了IEnumerable<FileStream>,ICollection<FileStream>,IList<FileStream>,同时会实现IEnumerable<Stream>,ICollection<Stream>,IList<Stream>,IEnumerable<Object>,ICollection<Object>,IList<Object>,所以fsArray可以作为参数传递给以下方法:

        void M1(IList<FileStream> fsList) { }
        
void M2(ICollection<Stream> sCollection) { }
        
void M3(IEnumerable<Object> oEnumerable) { }

但是,对于一维0基的值类型的数组,在创建时,会自动实现了IEnumerable<T>,ICollection<T>,IList<T>,T为这个数组的类型;但是不会为其基类实现接口。如DateTime[] dtArray;这个值类型dtArray不能传递给上面的M3方法。


13.4    数组的传递和返回
    Array.Copy返回的是浅Copy。
    数组最好是0长度,而不是null

13.5    非0基数组
使用Array.CreateInstance方法,可以指定数组中元素类型,数组维数,数组下界,数组每一维中元素个数,有若干重载,如下:

            //重载1,数组长度
            Decimal[] fsArray = (Decimal[])Array.CreateInstance(typeof(Decimal), 2);

            
//重载2,数组长度用一个数组表示,指定多维数组的维数和各维上的长度4和5
            Int32[] lengths = 45 };
            Decimal[,] fsArray 
= (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths);

            
//重载3,最后一个参数指定数组下界
            Decimal[] fsArray = (Decimal[])Array.CreateInstance(typeof(Decimal), 21);

            
//重载4,最后一个数组参数,指定数组各维上的下界
            Int32[] lengths = 45 };
            Int32[] lowerBounds 
= 12 };
            Decimal[,] fsArray 
= (Decimal[,])Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);

            
//重载5,创建一个三维数组,后三个参数分别指定各维的长度
            Decimal[,,] fsArray = (Decimal[,,])Array.CreateInstance(typeof(Decimal), 234);

可以使用GetLowerBound(维数);与GetUpperBound(维数);获取数组的边界
在一位数组fsArray中,可以使用fsArray数组对象的GetValue()和SetValue()方法来操作数组,但是比较慢。

13.6    数组访问性能
对于1维数组,0基的typeof为String.String[];非零基为String.String[*]
对于多维数组,都会显示String.String[,]。在运行时,CLR将多维数组视为非零基数组。

访问1维零基数组 比 非零基1维数组 和 多维数组 速度快很多。这是因为:
    1.有特殊的IL指令,用于处理1维零基数组,而不用在索引中减去偏移量
    2.JIT会将索引范围检查代码从循环中取出,从而只执行一次检查。

关于for循环遍历数组:

            Int32 a = new Int32[5];

            
for (Int32 index = 0; index < a.Length; a++)
            

                
//对a[index]进行操作
            }

Length是一个数组属性,调用一次后,JIT会将结果存入一个临时变量,以后每次循环迭代访问的都是这个变量,而不是再次计算长度,从而速度更快。
在循环前,JIT编译器会自动生成代码检测有效范围:if((Length-1) < a.GetUpperBound(0)),只会检测一次。
如果将Length保存在一个本地变量,在for循环时每次都会比较该变量,反而会损害性能。

以上只适用于0基数组。非零基数组中,JIT编译器在循环中,每次都要检查制指定索引范围是否有效,此外还要从指定索引减去数组下界,从而降低了性能。

提升性能的两个办法:
    使用交错数组(数组的数组)来代替多维数组。
    使用非安全数组代替非零基数组,在访问数组时关闭索引边界检查——只适用于基元值类型。

使用非安全代码访问2维数组:

        //用不安全代码访问2维数组中所有元素
        public static unsafe void Unsafe2DimArrayAccess(Int32[,] a)
        
{
            Int32 dim0LowIndex 
= 0//等价于a.GetLowerBound(0)
            Int32 dim0HighIndex = a.GetUpperBound(0);

            Int32 dim1LowIndex 
= 0//等价于a.GetLowerBound(1)
            Int32 dim1HighIndex = a.GetUpperBound(1);

            Int32 dim0Elements 
= dim0HighIndex - dim0LowIndex;

            
fixed (Int32* pi = &a[00])
            
{
                
for (Int32 x = dim0LowIndex; x <= dim0HighIndex; x++)
                
{
                    Int32 baseOfDim 
= x * dim0Elements;

                    
for (Int32 y = dim1LowIndex; y <= dim1HighIndex; y++)
                    
{
                        Int32 element 
= pi[baseOfDim + y];
                    }

                }

            }

        }


13.7    非安全数组和内嵌数组

非安全数组可以访问以下元素:
    托管堆上的数组
    非托管堆的数组
    线程堆栈上的数组

在性能第一的前提下,避免在堆上分配数组对象,应该在线程堆栈上分配——使用stackalloc
stackalloc只适用于创建1维0基值类型数组,而且值类型中决不能包括任何引用类型。见以下方法,将一个字符串倒置:

        public static void StackallocDemo()
        
{
            
unsafe
            
{
                
const Int32 width = 20;

                
//在堆栈上分配数组
                Char* pc = stackalloc Char[width];

                String s 
= "Jax Bao";

                
for (Int32 index = 0; index < width; index++)
                
{
                    pc[width 
- index - 1= (index < s.Length) ? s[index] : ' ';
                }


                String newS 
= new String(pc, 0, width);

                Console.WriteLine(newS.Trim());
            }

        }

在struct中定义数组,一般来说,数组本身在struct的外部。
要使数组内嵌在struct中,要遵从:
    1.struct要用unsafe标记
    2.数组字段要用fixed标记
    3.数组必须是1维0基的
    4.数组类型必须是基元值类型

采用内嵌数组实现的方法,将一个字符串倒置:

        public static void InlineArrayDemo()
        
{
            
unsafe
            
{
                CharArray ca;   
//在堆栈上分配数组
                Int32 widthInBytes = sizeof(CharArray);
                Int32 width 
= widthInBytes / 2;

                String s 
= "Jax Bao";

                
for (Int32 index = 0; index < width; index++)
                
{
                    ca.Characters[width 
- index - 1= (index < s.Length) ? s[index] : ' ';
                }


                String newS 
= new String(pc, 0, width);

                Console.WriteLine(newS.Trim());
            }

        }


        
public unsafe struct CharArray
        

            
public fixed Char Characters[20];
        }

 

 


 

posted @ 2007-09-26 16:33  包建强  Views(2507)  Comments(0Edit  收藏  举报