[CLR via C#]16. 数组
數(shù)組是允許將多個數(shù)據(jù)項當作一個集合來處理的機制。CLR支持一維數(shù)組、多維數(shù)組和交錯數(shù)據(jù)(即由數(shù)組構成的數(shù)組)。所有數(shù)組類型都隱式地從System.Array抽象類派生,后者又派生自System.Object。這意味著數(shù)組始終是引用類型,是在托管堆上分配的。在你應用程序的變量或字段中,包含的是對數(shù)組的引用,而不是包含數(shù)組本身的元素。下面的代碼更清楚的說明了這一點:
Int32[] myIntegers; //聲明一個數(shù)組引用 myIntegers = new int32[100] //創(chuàng)建含有100個Int32的數(shù)組在第一行代碼中,myIntegers變量能指向一個一維數(shù)組(由Int32值構成)。myIntegers剛開始被設為null,因為當時還沒有分配數(shù)組。第二行代碼分配了含有100個Int32值的一個數(shù)組,所有Int32都被初始化為0。由于數(shù)組是引 用類型,所有托管堆上還包含一個未裝箱Int32所需要的內存塊。實際上,除了數(shù)組元素,數(shù)字對象占據(jù)的內存塊還包含一個類型對象指針、一個同步塊索引和一些額外的成員(overhead)。該數(shù)組的內存塊地址被返回并保存到myIntegers變量中。
C#也支持多維數(shù)組。下面演示了幾個多維數(shù)組的例子:
// 創(chuàng)建一個二維數(shù)組,由Double值構成 Double[,] myDoubles = new Double[10,20]; // 創(chuàng)建一個三位數(shù)組,由String引用構成 String[,,] myStrings = new String[5,3,10];CLR還支持交錯數(shù)組,即由數(shù)組構成的數(shù)組。下面例子演示了如何創(chuàng)建一個多邊形數(shù)組,其中每一個多邊形都由一個Point實例數(shù)組構成。
// 創(chuàng)建一個含有Point數(shù)組的一維數(shù)組 Point[][] myPolygons = new Point[3][]; // myPolygons[0]引用一個含有10個Point實例的數(shù)組 myPolygons[0] = new Point[10]; // myPolygons[1]引用一個含有20個Point實例的數(shù)組 myPolygons[1] = new Point[20]; // myPolygons[2]引用一個含有30個Point實例的數(shù)組 myPolygons[2] = new Point[30]; // 顯示第一個多邊形中的Point for (Int32 x =0 ; x < myPolygons[0].Length; x++) {Console.WriteLine(myPolygons[0][x]); }注意:CLR會驗證數(shù)組索引的有效性。換句話說,不能創(chuàng)建一個含有100個元素的數(shù)組(索引編號為0到99),又試圖訪問索引為-5或100的元素。
一、始化數(shù)組元素
前面展示了如何創(chuàng)建一個數(shù)組對象,以及如何初始化數(shù)組中的元素。C#允許用一個語句來同時做兩件事。例如:
String[] names = new String[] { "Aidan", "Grant" };大括號中的以逗號分隔的數(shù)據(jù)成為數(shù)組初始化器。每個數(shù)據(jù)項都可以是一個任意復雜度的表達式;在多維數(shù)組的情況下,則可以是一個嵌套的數(shù)組初始化器。可利用C#的隱式類型的數(shù)組功能讓編譯器推斷數(shù)組元素的類型。注意,下面這一行代碼沒有在new和[]之間指定類型:
var names = new[] { "Aidan", "Grant", null};在上一行中,編譯器檢查數(shù)組中用于初始化數(shù)組元素的表達式的類型,并選擇所有元素最接近的共同基類作為數(shù)組的類型。在本例中,編譯器發(fā)現(xiàn)兩個String和一個null。由于null可隱式轉型成為任意引用類型(包括String),所以編譯器推斷應該創(chuàng)建和初始化一個由String引用構成的數(shù)組。
給定一下代碼:
var names = new[] { "Aidan", "Grant", 123};編譯器是會報錯的,雖然String類和Int32共同基類是Object,意味著編譯器不得不創(chuàng)建Object引用了一個數(shù)組,然后對123進行裝箱,并讓最后一個數(shù)組元素引用已裝箱的,值為123的一個Int32。但C#團隊認為,隱式對數(shù)組?元素進行裝箱是一個代價昂貴的操作,所以要做編譯時報錯。
在C#中還可以這樣初始化數(shù)組:
String[] names = { "Aidan", "Grant" };但是C#不允許在這種語法中使用隱式類型的局部變量:
var names = { "Aidan", "Grant" };最后來看下"隱式類型的數(shù)組"如何與"匿名類型"和"隱式類型的局部變量"組合使用。
// 使用C#的隱式類型的局部變量、隱式類型的數(shù)組和匿名類型 var kids = new[] {new { Name="Aidan" }, new { Name="Grant" }}; // 示例用法 foreach (var kid in kids)Console.WriteLine(kid.Name);輸出結果:
Aidan
Grant
二、數(shù)組轉型
對于元素為引用類型的數(shù)組,CLR允許將數(shù)組元素從一種類型隱式轉型到另一種類型。為了成功轉型,兩個數(shù)組類型必須維數(shù)相等,而且從源類型到目標類型,必須存在一個隱式或顯示轉換。CLR不允許將值類型元素的數(shù)組轉型為其他任何類型。(不過為了模擬實現(xiàn)這種效果,可利用Array.Copy方法創(chuàng)建一個新數(shù)組并在其中填充數(shù)據(jù))。下面演示了數(shù)組轉型過程:
Array.Copy方法的作用不僅僅是將元素從一個數(shù)組復制到另一個數(shù)組。Copy方法還能正確處理內存的重疊區(qū)域。?
Copy方法還能在復制每一個數(shù)組元素時進行必要的類型轉換。Copy方法能執(zhí)行以下轉換:
1)將值類型的元素裝箱為引用類型的元素,比如將一個Int32[]復制到一個Object[]中。
2)將引用類型的元素拆箱為值類型的元素,比如將一個Object[]復制到Int32[]中。
3)加寬CLR基元值類型,比如將一個Int32[]的元素復制到一個Double[]中。
4)在兩個數(shù)組之間復制時,如果僅從數(shù)組類型證明不了兩者的兼容性。
在某些情況下,將數(shù)組從一種類型轉換為另一種類型是非常有用的。這種功能稱為數(shù)據(jù)協(xié)變性。利用數(shù)組協(xié)變性時,應該清楚由此帶來的性能損失。
注意:如果只需要把數(shù)組中某些元素復制到另一個數(shù)組,可以選擇System.Buffer的BlockCopy方法,它的執(zhí)行速度比Array.Copy方法快。不過,Buffer的BlockCopy方法只支持基元類型,不提供像Array的Copy方法那樣的轉型能力。方法的Int32參數(shù)代表的是數(shù)組中的字節(jié)偏移量,而非元素索引。如果需要可靠的將一個數(shù)組中的元素復制到另一個數(shù)組,應該使用System.Array的ConstrainedCopy方法,該方法能保證不破壞目標數(shù)組中的數(shù)組的前提下完成復制,或者拋出異常。另外,它不執(zhí)行任何裝箱、拆箱或向下類型轉換。
三、所有數(shù)組都隱式派生自System.Array
如果像下面這樣聲明一個數(shù)組變量:
FileStream[] fsArray;CLR會為AppDomain自動創(chuàng)建一個FileStream[]類型。這個類型將隱式派生自System.Array類型;因此,System.Array類型定義的所有實例方法和屬性都將有FileStream[]繼承,使這些方法和屬性能通過fsArray變量調用。
四、所有數(shù)組都隱式實現(xiàn)IEnumerable,ICollection和IList
許多方法都能操作各種集合對象,因為在聲明它們時,使用了IEnumerable,ICollection和IList等參數(shù)。可以將數(shù)組傳給這些方法,因為System.Array也實現(xiàn)了這三個接口。System.Array之所以實現(xiàn)這些非泛型接口,是因為這些接口將所有元素都視為Systm.Object。然而,最好讓System.Array實現(xiàn)這個接口的泛型形式,提供更好的編譯時類型安全性和更好的性能。
五、數(shù)組的傳遞和返回
數(shù)組作為實參傳給一個方法時,實際傳遞的是對該數(shù)組的引用。因此,被調用的方法能修改數(shù)組中的元素。如果不想被修改,必須生成數(shù)組的一個拷貝,并將這個拷貝傳給方法。注意,Array.Copy方法執(zhí)行的是淺拷貝。
有的方法返回一個對數(shù)組的引用。如果方法構造并初始化數(shù)組,返回數(shù)組引用是沒有問題的。但假如方法返回的是對一個字段維護的內部數(shù)組的引用,就必須決定是否向讓該方法的調用者直接訪問這個數(shù)組及其元素。如果是就可以返回數(shù)組引用。但是通常情況下,你并不希望方法的調用這獲得這個訪問權限。所以,方法應該構造一個新數(shù)組,并調用Array.Copy返回對新數(shù)組的一個引用。
如果定義一個返回數(shù)組引用的方法,而且該數(shù)組不包含元素,那么方法既可以返回null,又可以放回對包含另個元素的一個數(shù)組的引用。實現(xiàn)這種方法時,Microsoft強烈建議讓它返回后者,因為這樣做能簡化調用該方法時需要的代碼。
// 這段代碼更容易寫,更容易理解 Appointment[] app = GetAppointmentForToday(); for (Int32 a =0; a< app.Length; a++) { // 對app[a]執(zhí)行操作 }如果返回null的話:
// 寫起來麻煩,不容易理解 Appointment[] app = GetAppointmentForToday(); if( app !=null ) { for (Int32 a =0; a< app.Length; a++) { // 對app[a]執(zhí)行操作} }六、創(chuàng)建下限非零的數(shù)組
可以調用數(shù)組的靜態(tài)CreateInstance方法來動態(tài)創(chuàng)建自己的數(shù)組。該方法有若干個重載版本,允許指定數(shù)組元素的類型、數(shù)組的維數(shù)、每一維的下限和每一維的元素數(shù)目。CreateInstance為數(shù)組分配內存,將參數(shù)信息保存到數(shù)組的內存塊的額外開銷(overhead)部分。然后返回對該數(shù)組的一個引用。
七、數(shù)組的訪問性能
CLR內部實際支持兩種不同的數(shù)組
1)下限為0的意味數(shù)組。這些數(shù)組有時稱為SZ數(shù)組或向量。
2)下限未知的一維或多維數(shù)組。
可執(zhí)行一下代碼來實際地查看不同種類的輸出
public static void Go() {
Array a;// 創(chuàng)建一個一維數(shù)組的0基數(shù)組,其中不包含任何元素 a = new String[0];Console.WriteLine(a.GetType()); // System.String[]// 創(chuàng)建一個一維數(shù)組的0基數(shù)組,其中不包含任何元素 a = Array.CreateInstance(typeof(String), new Int32[] { 0 }, new Int32[] { 0 });Console.WriteLine(a.GetType()); // System.String[]// 創(chuàng)建一個一維數(shù)組的1基數(shù)組,其中不包含任何元素 a = Array.CreateInstance(typeof(String), new Int32[] { 0 }, new Int32[] { 1 });Console.WriteLine(a.GetType()); // System.String[*] <-- 注意! Console.WriteLine();// 創(chuàng)建一個二維數(shù)組的0基數(shù)組,其中不包含任何元素 a = new String[0, 0];Console.WriteLine(a.GetType()); // System.String[,]// 創(chuàng)建一個二維數(shù)組的0基數(shù)組,其中不包含任何元素 a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[] { 0, 0 });Console.WriteLine(a.GetType()); // System.String[,]// 創(chuàng)建一個二維數(shù)組的1基數(shù)組,其中不包含任何元素 a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[] { 1, 1 });Console.WriteLine(a.GetType()); // System.String[,]} }
對于一維數(shù)組,0基數(shù)組顯示的類型名稱是System.String[],但1基數(shù)組顯示的是System.String[*]。*符號表示CLR知道該數(shù)組不是0基的。注意,C#不允許聲明String[*]類型的變量,因此不能使用C#語法來訪問一維的非0基數(shù)組。盡管可以調用Array的GetValue和SetValue方法來訪問數(shù)組的元素,但速度會比較慢,畢竟有方法調用的開銷。
對于多維數(shù)組,0基和1基數(shù)組會顯示同樣的類型名稱:System.String[,]。在運行時,CLR將對所有多維數(shù)組都視為非0基數(shù)組。這自然會人覺得應該顯示為System.String[*,*]。但是,對于多維數(shù)組,CLR決定不用*符號,避免開發(fā)人員對*產生混淆。
訪問一維0基數(shù)組的元素比訪問非0基數(shù)組或多維數(shù)組的元素稍快一些。首先,有一些特殊的IL指令,比如newarr,ldelem,ldelema等用于處理一維0基數(shù)組,這些特殊IL指令會導致JIT編譯器生成優(yōu)化代碼。其次,JIT編譯器知道for循環(huán)要反問0到Length-1之間的數(shù)組元素。所以,JIT編譯器生成的代碼會在運行時測試所有數(shù)組元素的訪問都在數(shù)組有效訪問內。
如果很關系性能,請考慮由數(shù)組構成的數(shù)組(即交錯數(shù)組)來替代矩形數(shù)組。
下面C#代碼演示了訪問二維數(shù)組的三種方式:
internal static class MultiDimArrayPerformance { private const Int32 c_numElements = 10000;public static void Go() { const Int32 testCount = 10; Stopwatch sw;// 聲明一個二維數(shù)組 Int32[,] a2Dim = new Int32[c_numElements, c_numElements];// 將一個二維數(shù)組聲明為交錯數(shù)組 Int32[][] aJagged = new Int32[c_numElements][]; for (Int32 x = 0; x < c_numElements; x++) aJagged[x] = new Int32[c_numElements];// 1: 用普通的安全技術訪問數(shù)組中的所有元素 sw = Stopwatch.StartNew(); for (Int32 test = 0; test < testCount; test++) Safe2DimArrayAccess(a2Dim); Console.WriteLine("{0}: Safe2DimArrayAccess", sw.Elapsed);// 2: 用交錯數(shù)組技術訪問數(shù)組中的所有元素 sw = Stopwatch.StartNew(); for (Int32 test = 0; test < testCount; test++) SafeJaggedArrayAccess(aJagged); Console.WriteLine("{0}: SafeJaggedArrayAccess", sw.Elapsed);// 3: 用unsafe訪問數(shù)組中的所有元素 sw = Stopwatch.StartNew(); for (Int32 test = 0; test < testCount; test++) Unsafe2DimArrayAccess(a2Dim); Console.WriteLine("{0}: Unsafe2DimArrayAccess", sw.Elapsed); Console.ReadLine(); }private static Int32 Safe2DimArrayAccess(Int32[,] a) {Int32 sum = 0;for (Int32 x = 0; x < c_numElements; x++){for (Int32 y = 0; y < c_numElements; y++){sum += a[x, y];}}return sum; }private static Int32 SafeJaggedArrayAccess(Int32[][] a) {Int32 sum = 0;for (Int32 x = 0; x < c_numElements; x++){for (Int32 y = 0; y < c_numElements; y++){sum += a[x][y];} } return sum; }private static unsafe Int32 Unsafe2DimArrayAccess(Int32[,] a) {Int32 sum = 0;fixed (Int32* pi = a){for (Int32 x = 0; x < c_numElements; x++){Int32 baseOfDim = x * c_numElements;for (Int32 y = 0; y < c_numElements; y++){sum += pi[baseOfDim + y];}}}return sum; } }本機結果是:
可以看出,安全二維數(shù)組訪問技術最慢。安全交錯數(shù)組訪問時間略少于安全二維數(shù)組。不過應該注意的是:創(chuàng)建交錯數(shù)組所花的時間多于創(chuàng)建多維數(shù)組所花的時間,因為創(chuàng)建交錯數(shù)組時,要求在堆上為每一維分配一個對象,造成垃圾回收器的周期性活動。所以你可以這樣權衡:如果需要創(chuàng)建大量"多個維的數(shù)組",而不會頻繁訪問它的元素,那么創(chuàng)建多維數(shù)組就要快點。如果"多個維的數(shù)組"只需創(chuàng)建一次,而且要頻繁訪問它的元素,那么交錯數(shù)組性能要好點。當然,大多數(shù)應用中,后一種情況更常見。
最后請注意,不安全和安全二維數(shù)組訪問技術的速度大致相同。但是,考慮到它訪問是單個二維數(shù)組(產生一次內存分配),二不像交錯數(shù)組那樣需要許多次內存分配。所以它的速度是所有技術中最快的。
八、不安全的數(shù)組訪問和固定大小的數(shù)組
如果性能是首要目標,請避免在堆上分配托管的數(shù)組對象。相反,應該在線程棧上分配數(shù)組,這是通過C#的 stackalloc語句來完成的。stackalloc語句只能創(chuàng)建一維0基、由值類型元素構成的數(shù)組,而且值類型絕對不能包 含任何引用類型的字段。當然,在棧上分配的內存(數(shù)組)會在方法返回時自動釋放。
以下代碼顯示如何使用C#的stackalloc語句:
internal static class StackallocAndInlineArrays { public static void Go() { StackallocDemo(); InlineArrayDemo(); }private static void StackallocDemo() { unsafe { const Int32 width = 20; Char* pc = stackalloc Char[width]; // 在棧上分配數(shù)組 String s = "Jeffrey Richter"; // 15 個字符for (Int32 index = 0; index < width; index++) { pc[width - index - 1] = (index < s.Length) ? s[index] : '.'; } //顯示".....rethciR yerffeJ" Console.WriteLine(new String(pc, 0, width)); } }private static void InlineArrayDemo() { unsafe { CharArray ca; // 在棧上分配數(shù)組 Int32 widthInBytes = sizeof(CharArray); Int32 width = widthInBytes / 2;String s = "Jeffrey Richter"; // 15 個字符for (Int32 index = 0; index < width; index++) { ca.Characters[width - index - 1] = (index < s.Length) ? s[index] : '.'; } //顯示".....rethciR yerffeJ" Console.WriteLine(new String(ca.Characters, 0, width)); } }private unsafe struct CharArray { // 這個數(shù)組以內聯(lián)的方式嵌入結構 public fixed Char Characters[20]; } }通常,因為數(shù)組是引用類型,所以在一個結構中定義的數(shù)組字段實際只是指向數(shù)組的一個指針;數(shù)組本身在結構的內存的外部。不過,也可以像上述代碼中的CharArray結構那樣,直接將數(shù)組嵌入結構中。要在結構中直接嵌入一個數(shù)組,需要滿足以下幾個要求:
1)類型必須是結構(值類型);不能在類(引用類型)中嵌入數(shù)組。
2)字段或其定義結構必須用unsafe關鍵字標記
3)數(shù)組字段必須使用fixed關鍵字標記
4)數(shù)組必須是一維0基數(shù)組。
5)數(shù)組的元素類型必須是一下類型之一:Boolean,Char,SByte,Byte,Int16,Int32,UInt16,UInt32,Int64,UInt64,Single或Double。
內聯(lián)(內嵌)數(shù)組常用于和非托管代碼進行互操作,而且非托管數(shù)據(jù)結構也有一個內聯(lián)數(shù)組。不過,也可用于其他情況。
轉載于:https://www.cnblogs.com/zxj159/p/3569500.html
總結
以上是生活随笔為你收集整理的[CLR via C#]16. 数组的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【LeetCode】Minimum De
- 下一篇: 对数据库连接池的理解