簡體   English   中英

如何以及何時放棄在 C# 中使用數組?

[英]How and when to abandon the use of arrays in C#?

我一直被告知向數組添加元素是這樣發生的:

創建數組+1元素的空副本,然后將原始數組中的數據復制到其中,然后加載新元素的新數據

如果這是真的,那么由於內存和 CPU 利用率,在需要大量元素活動的場景中使用數組是禁忌的,對嗎?

如果是這種情況,當您將添加大量元素時,您是否應該盡量避免使用數組? 你應該改用 iStringMap 嗎? 如果是這樣,如果您需要兩個以上的維度並且需要添加大量元素,會發生什么情況。 您是否只是在性能上受到影響,還是應該使用其他東西?

查看通用List<T>作為數組的替代。 它們支持大多數數組所做的事情,包括根據需要分配初始存儲大小。

這實際上取決於“添加”的含義。

如果你的意思是:

T[] array;
int i;
T value;
...
if (i >= 0 && i <= array.Length)
    array[i] = value;

然后,不,這不會創建一個新數組,實際上是改變.NET中任何類型的IList的最快方法。

但是,如果你使用的是ArrayList,List,Collection等,那么調用“Add”方法可能會創建一個新數組 - 但是他們對它很聰明,它們不只是調整1個元素,它們幾何增長,所以如果你每隔一段時間添加很多值就必須分配一個新的數組。 即使這樣,如果您知道要添加多少元素,也可以使用“容量”屬性強制它list.Capacity += numberOfAddedElementslist.Capacity += numberOfAddedElements

一般來說,我更喜歡避免使用數組。 只需使用List <T>。 它在內部使用動態大小的數組,並且對於大多數用途來說足夠快。 如果您正在使用多維數組,請使用List <List <List <T >>>(如果必須)。 它在內存方面並沒有那么糟糕,並且添加項目要簡單得多。

如果您處於需要極速的0.1%的使用率,請確保在您嘗試優化之前,您的列表訪問確實是問題。

如果您要添加/刪除元素很多,只需使用List。 如果它是多維的,您可以始終使用List <List <int >>或其他東西。

另一方面,如果您主要執行的操作是遍歷列表,則列表的效率低於數組,因為數組都位於CPU緩存中的一個位置,列表中的對象遍布整個位置。

如果您想使用數組進行有效讀取,但是您將經常“添加”元素,則有兩個主要選項:

1)將其生成為List(或列表列表),然后使用ToArray()將其轉換為有效的數組結構。

2)將數組分配給比您需要的更大,然后將對象放入預先分配的單元格中。 如果您最終需要的元素數量超過預先分配的數量,則可以在數組填充時重新分配數組,每次都會增加一倍。 這使得O(log n)調整大小性能而不是O(n),就像使用reallocate-once-each-add數組一樣。 請注意,這幾乎是StringBuilder的工作原理,為您提供了一種更快的方式來連續追加字符串。

什么時候放棄使用數組

  1. 首先, 當數組的語義與您的意圖匹配時 - 需要一個動態增長的集合? 一套不允許重復的套裝? 一個必須保持不變的集合? 在所有情況下都避免使用數組。 這是99%的案例。 只是陳述明顯的基本觀點。

  2. 其次, 當你沒有編寫絕對性能關鍵性時 - 大約95%的情況。 陣列有更好的表現勉強尤其是在迭代 它幾乎永遠不會重要。

  3. 當你沒有params關鍵字的參數強迫時 - 我只是希望params接受任何IEnumerable<T>或甚至更好的語言構造本身來表示序列 (而不是框架類型)。

  4. 當您編寫遺留代碼或處理互操作時

簡而言之,您實際上需要一個陣列非常罕見。 我會補充為什么可以避免它?

  1. 避免數組imo的最大原因是概念性的。 數組更接近實現,更遠離抽象。 陣列傳達更多的則是如何 比做是對的高級語言的精神來完成 這並不奇怪,考慮到陣列更接近金屬,它們直接來自特殊類型(盡管內部數組是一個類)。 不是教學,但數組確實轉化為很少需要的語義。 最有用和最頻繁的語義是具有任何條目的集合,具有不同項目的集合,鍵值映射等,具有可添加,只讀,不可變,順序相關變體的任何組合。 考慮一下,您可能需要一個可添加的集合,或者只有預定義項目的只讀集合,無需進一步修改,但是您的邏輯看起來像“我想要一個動態可添加的集合,但只有固定數量的集合,它們也應該是可修改的” “? 我會說非常罕見。

  2. Array是在pre-generics時代設計的,它模仿了許多運行時黑客的通用性,它會在這里和那里顯示它的奇怪之處。 我找到的一些漁獲物:

    1. 破壞的協方差。

       string[] strings = ... object[] objects = strings; objects[0] = 1; //compiles, but gives a runtime exception. 
    2. 數組可以為您提供結構參考! 這與其他地方不同。 一個樣品:

       struct Value { public int mutable; } var array = new[] { new Value() }; array[0].mutable = 1; //<-- compiles ! //a List<Value>[0].mutable = 1; doesnt compile since editing a copy makes no sense print array[0].mutable // 1, expected or unexpected? confusing surely 
    3. 運行時實現的方法如ICollection<T>.Contains對於結構和類可以是不同的 這不是什么大問題,但如果你忘記為參考類型正確覆蓋非泛型Equals ,期望泛型集合尋找通用Equals ,你將得到不正確的結果。

       public class Class : IEquatable<Class> { public bool Equals(Class other) { Console.WriteLine("generic"); return true; } public override bool Equals(object obj) { Console.WriteLine("non generic"); return true; } } public struct Struct : IEquatable<Struct> { public bool Equals(Struct other) { Console.WriteLine("generic"); return true; } public override bool Equals(object obj) { Console.WriteLine("non generic"); return true; } } class[].Contains(test); //prints "non generic" struct[].Contains(test); //prints "generic" 
    4. T[]上的Length屬性和[]索引器似乎是可以通過反射訪問的常規屬性(這應該涉及一些魔法),但是當涉及到表達式樹時,你必須吐出與編譯器完全相同的代碼。 ArrayLengthArrayIndex方法可以單獨完成。 這里有一個問題 另一個例子:

       Expression<Func<string>> e = () => new[] { "a" }[0]; //e.Body.NodeType == ExpressionType.ArrayIndex Expression<Func<string>> e = () => new List<string>() { "a" }[0]; //e.Body.NodeType == ExpressionType.Call; 

如何放棄使用數組

最常用的替代品是List<T> ,它具有更干凈的API。 但它是一個動態增長的結構,這意味着您可以在末尾添加List<T>或插入任何容量的任何位置。 沒有什么可以替代數組的確切行為,但人們大多使用數組作為只讀集合,在這種集合中你不能添加任何東西。 替代品是ReadOnlyCollection<T> 我帶這個擴展方法:

public ReadOnlyCollection<T> ToReadOnlyCollection<T>(IEnumerable<T> source)
{
    return source.ToList().AsReadOnly();
}

調整數組大小時,必須分配新數組,並復制內容。 如果您只是修改數組的內容,那只是一個內存賦值。

因此,當您不知道數組的大小或者大小可能會發生變化時,您不應該使用數組。 但是,如果您有一個固定長度的數組,它們是一種通過索引檢索元素的簡單方法。

ArrayList和List在需要時將數組增加多個(我認為是通過加倍大小,但我沒有檢查源)。 在構建動態大小的數組時,它們通常是最佳選擇。

當您的基准測試表明數組調整大小會嚴重降低您的應用程序的速度時(請記住 - 過早優化是所有惡意的根源),您可以評估編寫具有調整調整大小行為的自定義數組類。

通常,如果您必須具有BEST索引查找性能,則最好首先構建List,然后將其轉換為數組,從而首先支付一小部分懲罰,但后來避免任何后果。 如果問題是您將不斷添加新數據並刪除舊數據,那么您可能希望使用ArrayList或List以方便使用,但請記住它們只是特殊情況的數組。 當他們“成長”時,他們會分配一個全新的陣列並將所有內容復制到其中,這非常慢。

ArrayList只是一個在需要時增長的數組。 添加是分攤O(1),只是要小心確保調整大小不會發生在一個糟糕的時間。 插入是O(n)必須移動右側的所有項目。 刪除是O(n)必須移動右側的所有項目。

同樣重要的是要記住List不是鏈表。 它只是一個類型化的ArrayList。 列表文檔確實注意到它在大多數情況下表現更好,但沒有說明原因。

最好的辦法是選擇適合您問題的數據結構。 這取決於很多事情,因此您可能希望瀏覽System.Collections.Generic命名空間。

在這種特殊情況下,我會說,如果你能想出一個好的關鍵值, 詞典將是你最好的選擇。 它具有接近O(1)的插入和移除。 但是,即使使用Dictionary,也必須注意不要讓它調整內部數組的大小(O(n)操作)。 最好通過在構造函數中指定更大,然后期望使用的初始容量來為它們提供大量空間。

-Rick

您可以做的最好的事情是盡可能預先分配盡可能多的內存。 這將阻止.NET進行額外調用以獲取堆上的內存。 如果失敗那么分配五個或任何數量的塊對你的應用程序有意義是有意義的。

這是一個你可以真正應用於任何事情的規則。

應使用長度定義標准數組,該長度保留連續塊中所需的所有內存。 將項添加到數組會將其放入已保留內存塊中。

對於少數寫入和許多讀取,數組非常有用,特別是那些具有迭代性質的讀取 - 對於其他任何內容,使用許多其他數據結構之一。

你是對的,陣列非常適合查找。 然而,對陣列大小的修改是昂貴的。

您應該在要修改數組大小的方案中使用支持增量大小調整的容器。 您可以使用允許您設置初始大小的ArrayList,並且可以不斷檢查大小與容量,然后通過大塊增加容量以限制調整大小的數量。

或者您可以使用鏈接列表。 然而,看起來很慢......

關於各種數組類型的效率,這個論壇帖子可能會或可能沒有用處: C#數組 - 多維vs詞典

如果我認為我將在其生命周期中大量添加項目,那么我將使用List。 如果我確定在聲明它時集合的大小是多少,那么我將使用一個數組。

另一次我通常在List上使用數組是當我需要將一個集合作為對象的屬性返回時 - 我不希望調用者通過List的Add方法添加項目集合,而是希望他們將項目添加到集合中通過我的對象的界面。 在這種情況下,我將獲取內部List並調用ToArray並返回一個數組。

如果您要進行大量添加, 並且您不會進行隨機訪問(例如myArray[i] )。 您可以考慮使用鏈接列表( LinkedList<T> ),因為它永遠不會像List<T>實現那樣“增長”。 但請記住,您只能使用IEnumerable<T>接口真正訪問LinkedList<T>實現中的項目。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM