前言
如果需要使用相同的類型的多個對象,就可以使用集合和數組,這一節主要講解數組,其中會重點涉及到Span<T>結構和ArrayPool數組池。我們也會先涉及到簡單的數組、多維數組、鋸齒數組、Array類。
簡單的數組、多維數組、鋸齒數組
簡單的數組介紹
數組的聲明:
Int [] myArray;
初始化:
myArray=new int[4];
還可以:
Int [] myArray=new int []{1,2,3,4};
訪問數組:
myArray[0];
多維數組介紹
一般的數組(也稱一維數組)是用一個數字來索引,多維數組用兩個或兩個以上的數字進行索引。
聲明多維數組時中間以,隔開,我們下面聲明一個二維數組。
int [,] twodim=new int [3,3] int[,] twodim = { { 1,2,3}, { 4,5,6}, { 7,8,9} };
一個三維數組。
int[,,] threedim = { { { 1,2},{ 3,4} }, {{ 5,6},{ 7,8} }, { { 9,10},{ 11,12} } }; Console.WriteLine(threedim[0,1,1]);
鋸齒數組
二維數組圖形:
鋸齒數組
在聲明鋸齒數組的時候要依次放置左右括號。在初始化鋸齒數組時,只對第一對方括號中設置該數組包含的行數,定義各行中元素個數的第二個方括號設為空,因為這類數組的每一行包含不同的元素個數。
int[][] jagged = new int[3][]; jagged[0] = new int[2] { 1, 2 }; jagged[1] = new int[4] { 3, 4, 5, 6 }; jagged[2] = new int[3] { 7, 8, 9 };
Array類
創建數組:
Array intArray1 = Array.CreateInstance(typeof(int), 5); for (int i = 0; i < intArray1.Length; i++) { intArray1.SetValue(33, i); }
上面這段代碼,描述了Array數組的創建以及設置值。CreateInstance()方法第一個參數為元素的類型,第二個參數為定義數組的大小。SetValue()方法設置值第一個參數為設置IDE值,第二個參數為設置的索引。
復制數組:
int[] intArray1 = { 1,2}; int[] intArray2 = (int[])intArray1.Clone();
因為數組是引用類型的,所以將一個數組的變量賦予另一個數組變量,就會得到兩個引用同一個數組的變量,這是使用的是Clone()方法創建數組的淺表副本。使用Copy()方法也可以創建淺表副本,Clone()方法會創建一個數組,而Copy()方法必須傳遞階數相同且有足夠元素的已有數組。
排序:
class Program { static void Main(string[] args) { int[] list = { 34, 72, 13, 44, 25, 30, 10 }; Console.Write("原始數組: "); foreach (int i in list) { Console.Write(i + " "); } Console.WriteLine(); // 逆轉數組
Array.Reverse(list); Console.Write("逆轉數組: "); foreach (int i in list) { Console.Write(i + " "); } Console.WriteLine(); // 排序數組
Array.Sort(list); Console.Write("排序數組: "); foreach (int i in list) { Console.Write(i + " "); } Console.WriteLine(); } }
輸出:
原始數組: 34 72 13 44 25 30 10 逆轉數組: 10 30 25 44 13 72 34 排序數組: 10 13 25 30 34 44 72
在上述方法中Array.Sort()方法實現了數組的排序,而Array.Reverse()實現了數組的逆轉 。
ArrayPool數組池
接下來重點來了,本文的重點一,ArrayPool數組池。如果一個應用需要創建和銷毀許多的數組,垃圾收集器就要花費很多的功夫來做這些工作,為了較少垃圾收集器的工作,這里我們可以使用ArrayPool類來使用數組池。ArrayPool管理一個數組池,數組可以再這里租借內存,並且返回到這里。需要引用using System.Buffers;
創建數組池:
ArrayPool<int> arrayPool = ArrayPool<int>.Create(maxArrayLength:40000, maxArraysPerBucket: 10);
maxArrayLength的默認值是1024*10224字節(數組的長度),maxArraysPerBucket默認值是50(數組的數量)。
這里還可以使用以下方法來使用預定義的共享池。
ArrayPool<int> sharePool = ArrayPool<int>.Shared;
下面我們就一起看看如何去使用這個數組池吧:
class Program { static void Main(string[] args) { //定義數組池
ArrayPool<int> arrayPool = ArrayPool<int>.Create(maxArrayLength: 10000, maxArraysPerBucket: 10); //定義使用數組的長度
int arrayLenght = 5; int[] array = arrayPool.Rent(arrayLenght); //輸出數組的長度
Console.WriteLine($"定義數組長度:{arrayLenght},實際數組長度:{array.Length}"); //對數組進行賦值
array[0] = 0; array[1] = 1; array[2] = 2; array[3] = 3; array[4] = 4; //輸出數組的值
foreach (var item in array) { Console.WriteLine(item); } //將內存返回給數組池,clearArray設置True清除數組,下次調用時為空,設置False,保留數組,下次調用還是現在的值
arrayPool.Return(array, clearArray: true); foreach (var item in array) { Console.WriteLine(item); } } }
在上面事例中,我們使用Rent()方法請求池中的內存,Rent方法返回一個數組,其中至少包含所請求的元素個數。返回的數組可能會用到更多的內存,池中最少的請求為16個元素,緊接着是32,64,128以此類推。所以在上述例子中我們請求的長度為5,但是實際使用的元素個數為16個,多余的將根據類型對其賦值0或者null。
我們使用Return()方法將數組返回到池中,這里使用了一個可選參數clearArray,指定是否清除該數組,不清除的話下一個從池中租用這個數組的人可以讀取到其中的數據。清除數據可以避免這種情況,但是會消耗更多的CPU時間。
Span<T>
Span<T>介紹
為了快速訪問托管或非托管的連續內存,可以使用Spam<T>結構。一個可以使用Span<T>結構的例子就是數組,Span<T>結構在后台保存在連續的內存中,另一個例子就是長字符串。
使用Span<T>結構,可以直接訪問數組元素。數組的元素沒有復制,但是它們可以直接調用,並且比復制還快。
class Program { static void Main(string[] args) { int[] arr1 = { 3, 5, 7, 9, 11, 13, 15, 18, 19 }; var span1 = new Span<int>(arr1); span1[1] = 11; Console.WriteLine(arr1[1]); } }
輸出:
這里將創建的arr1數組傳遞給Span<T>,同時Span<T>類型提供了一個索引器,這里直接修改span1的第二個值,然后再輸出arr1數組中的第二個值,也是被其修改過得值。
Span<T>切片
Span<T>它一個強大的特性是,可以使用它訪問數組的部分或者切片,使用切片的時候不會復制數組元素,他們是從Span中直接訪問的。下面代碼介紹了創建切片的兩種方法:
class Program { static void Main(string[] args) { //定義簡單的數組
int[] arr2 = { 3, 5, 7, 9, 11, 13, 15, 18, 19, 20, 30, 40, 50, 60 }; //Span<T>對數組進行切片,訪問arr2數組,從第三個開始,取長度6個的一個數組。
var span3 = new Span<int>(arr2, start: 3, length: 6); //輸出切片中的值
foreach (var item in span3) { Console.WriteLine(item); } Console.WriteLine(""); Console.WriteLine(""); Console.WriteLine(""); //對span3進行切片處理,從第二個開始,去長度4個的一個數組
var span4 = span3.Slice(start: 2, length: 4); foreach (var item in span4) { Console.WriteLine(item); } } }
輸出:
使用Span<T>改變值
前面介紹了如何使用Span<T>的索引器,更改數組的元素,下面介紹的將會有更多的選項,關於修改元素的值及復制。
class Program { static void Main(string[] args) { //定義簡單的數組
int[] arr = { 3, 5, 7, 9, 11, 13, 15, 18, 19, 20, 30, 40, 50, 60 }; //將數組傳遞給span
var span = new Span<int>(arr); var span2 = new Span<int>(arr); //對span進行切片處理,從第四個開始
var span3 = span.Slice(start: 4 ); //調用clear方法,用0填充span3
span3.Clear(); foreach (var item in span3) { Console.WriteLine("span3的值:"+item); } Console.WriteLine("span3的長度:"+span3.Length); //創建新的切片span4,從span2開始,長度3
Span<int> span4 = span2.Slice(start: 3, length: 3); //調用Fill方法,用傳入的值填充span4
span4.Fill(42); foreach (var item in span4) { Console.WriteLine("span4的值"+item); } Console.WriteLine("span4的長度"+span4.Length); //將span4復制給span,復制失敗
span4.CopyTo(span); //將span復制給span3 復制失敗
if (!span.TryCopyTo(span3)) { Console.WriteLine("復制不了"); } } }
輸出:
上面事例中,顯示調用clear()方法,該方法用0填充Span,然后調用了Fill()方法,該方法用傳遞給Fill方法的值來填充Span,同時也可以將一個Span<T>復制給另一個Span<T>,這里先是采用的CopyTo,在這個方法中,如果另一個目標span不夠大,就會復制失敗,這里可以使用TryCopyTo來優化此功能,如果目標不夠大,將會返回false。以此來判斷是否復制成功。上面例子中span4長度為3,而span長度為14,這里是復制成功了,然后其下面的操作,因為span3的長度是10,span復制給span3失敗了。因為span3不夠大。
上面事例中提供了改變值的一些方法,當我們不需要對值進行改變,只需要對數組進行讀訪問的時候,我們可以使用ReadOnlySpan<T>。這里定義了只讀的Span
ReadOnlySpan<int> readonlySpan = new ReadOnlySpan<int>(arr);
總結
在本篇文章中,重點介紹了ArrayPool數組池和Span<T>結構,通過使用數組池,來降低數組創建和銷毀時消耗的性能,減少垃圾回收器的工作,使用Span<T>可以快速的訪問托管及非托管代碼,創建切片來對數組和長字符串進行一定的操作。更加高效的操作數組。
青少年是一個美好而又是一去不可再得的時期,是將來一切光明和幸福的開端。——加里寧
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=2n1gp5jweqww4
歡迎大家掃描下方二維碼,和我一起學習更多的C#知識