前言
如果需要使用相同的型別的多個物件,就可以使用集合和陣列,這一節主要講解陣列,其中會重點涉及到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]);
鋸齒陣列
二維陣列圖形:
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
鋸齒陣列
1 |
2 |
||
3 |
4 |
5 |
6 |
7 |
8 |
9 |
在宣告鋸齒陣列的時候要依次放置左右括號。在初始化鋸齒陣列時,只對第一對方括號中設定該陣列包含的行數,定義各行中元素個數的第二個方括號設為空,因為這類陣列的每一行包含不同的元素個數。
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>可以快速的訪問託管及非託管程式碼,建立切片來對陣列和長字串進行一定的操作。更加高效的運算元組。
青少年是一個美好而又是一去不可再得的時期,是將來一切光明和幸福的開端。——加裡寧
歡迎大家掃描下方二維碼,和我一起學習更多的C#知識