25

配列に要素を追加すると、次のようになるといつも言われています。

array + 1elementの空のコピーが作成され、元の配列のデータがそこにコピーされ、新しい要素の新しいデータがロードされます。

これが当てはまる場合、メモリとCPUの使用率が原因で、多くの要素アクティビティを必要とするシナリオ内で配列を使用することは禁じられています。

その場合、多くの要素を追加するときに、配列の使用をできるだけ避けようとすべきではありませんか?代わりにiStringMapを使用する必要がありますか?その場合、3つ以上のディメンションが必要で、多くの要素を追加する必要がある場合はどうなりますか。パフォーマンスの打撃を受けただけですか、それとも他に使用すべきものがありますか?

4

14 に答える 14

24

List<T>配列の代わりとしてジェネリックを見てください。これらは、必要に応じて初期ストレージ サイズを割り当てるなど、配列と同じことのほとんどをサポートします。

于 2008-09-16T19:27:27.453 に答える
12

これは、「追加」の意味によって異なります。

つまり:

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

いいえ、これは新しい配列を作成しません。実際、.NET であらゆる種類の IList を変更する最速の方法です。

ただし、ArrayList、List、Collection などを使用している場合は、「Add」メソッドを呼び出すと新しい配列作成される可能性があります。幾何学的に成長するため、たまにしか多くの値を追加しない場合は、新しい配列を割り当てる必要があります。その場合でも、追加する要素の数がわかっている場合は、「Capacity」プロパティを使用して事前に強制的に大きくすることができます ( list.Capacity += numberOfAddedElements)

于 2008-09-16T19:34:36.640 に答える
5

一般的に、私は配列の使用を避けることを好みます。List<T> を使用するだけです。動的にサイズ変更される配列を内部で使用し、ほとんどの使用に対して十分に高速です。多次元配列を使用している場合は、必要に応じて List<List<List<T>>> を使用してください。メモリの点ではそれほど悪くはなく、アイテムを追加するのがはるかに簡単です.

極端な速度を必要とする使用率が 0.1% に達している場合は、最適化を試みる前に、実際に問題となっているのはリスト アクセスであることを確認してください。

于 2008-09-16T19:29:20.460 に答える
3

要素を頻繁に追加/削除する場合は、リストを使用してください。多次元の場合は、いつでも List<List<int>> などを使用できます。

一方、ほとんどの作業がリストのトラバースである場合、リストは配列よりも効率的ではありません。なぜなら、配列はすべて CPU キャッシュ内の 1 か所にあり、リスト内のオブジェクトはあちこちに散らばっているからです。

効率的な読み取りのために配列を使用したいが、要素を頻繁に「追加」する場合は、2 つの主なオプションがあります。

1) リスト (またはリストのリスト) として生成し、ToArray() を使用して効率的な配列構造に変換します。

2) 配列を必要以上に大きくしてから、事前に割り当てられたセルにオブジェクトを配置します。事前に割り当てた要素よりもさらに多くの要素が必要になった場合は、配列がいっぱいになったときに配列を再割り当てし、毎回サイズを 2 倍にすることができます。これにより、O(n) ではなく、O(log n) のサイズ変更パフォーマンスが得られます。これは、StringBuilder が機能する方法とほとんど同じであり、文字列に継続的に追加するためのより高速な方法を提供することに注意してください。

于 2008-09-16T19:30:50.533 に答える
3

配列の使用をいつ放棄するか

  1. 何よりもまず、配列のセマンティクスが意図と一致しない場合- 動的に成長するコレクションが必要ですか? 重複を許さないセット?不変でなければならないコレクション?そのすべての場合で配列を避けてください。それが99%のケースです。明らかな基本的なポイントを述べているだけです。

  2. 第二に、絶対的なパフォーマンスの重要性のためにコーディングしていない場合- それはケースの約 95% です。配列は特に反復においてわずかにパフォーマンスが向上します。ほとんどの場合、問題になることはありません。

  3. キーワード付きの引数に強制されていない場合params- 私は、シーケンス(フレームワークの型ではなく)を表すために、言語構造自体をparams受け入れたいと思っていました。IEnumerable<T>

  4. レガシー コードを書いていないとき、または相互運用を扱っていないとき

要するに、実際に配列が必要になることは非常にまれです。なぜそれを避けることができるのかについて追加しますか?

  1. 配列を避ける最大の理由は概念的なものです。配列は実装に近く、抽象化にはほど遠いものです。配列は、何が行われたかよりも、どのように行われたかを伝えますこれは高級言語の精神に反します。配列が金属に近いことを考えると、それは驚くべきことではありません。配列は特別なタイプから直接出てきます (ただし、内部では配列はクラスです)。教育的ではありませんが、配列は実際には意味論的な意味に変換されますが、必要になることはほとんどありません。最も便利で頻繁に使用されるセマンティクスは、任意のエントリを含むコレクション、個別のアイテムを含むセット、キー値マップなど、追加可能、読み取り専用、不変、順序を尊重するバリアントの任意の組み合わせのセマンティクスです。これについて考えてみてください。追加可能なコレクション、または事前定義された項目を変更せずに読み取り専用のコレクションが必要になる場合がありますが、ロジックはどのくらいの頻度で「動的に追加可能なコレクションが必要ですが、それらの固定数のみが必要であり、それらも変更可能である必要があります」 "? 非常にまれです。

  2. 配列は、ジェネリック以前の時代に設計されたものであり、多くの実行時のハックでジェネリック性を模倣しており、あちこちでその奇妙な点を示しています。私が見つけたキャッチのいくつか:

    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. Lengthプロパティと[]インデクサーはT[]、リフレクションを介してアクセスできる通常のプロパティのように見えますが (これにはいくつかの魔法が必要です)、式ツリーに関しては、コンパイラが行うのとまったく同じコードを吐き出す必要があります。それを個別に行う方法がありますArrayLengthArrayIndexそのような質問の 1 つがここにあります。もう一つの例:

      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();
}
于 2013-11-11T13:23:52.467 に答える
2

一般に、最高のインデックス付きルックアップパフォーマンスが必要な場合は、最初にリストを作成してから配列に変換することをお勧めします。これにより、最初はわずかなペナルティが支払われますが、後で回避することができます。問題が継続的に新しいデータを追加し、古いデータを削除することである場合は、便宜上ArrayListまたはListを使用することをお勧めしますが、これらは特殊なケースの配列であることに注意してください。彼らが「成長」するとき、彼らは完全に新しい配列を割り当てて、それにすべてをコピーしますが、それは非常に遅いです。

ArrayListは、必要に応じて拡張される単なる配列です。Addは償却されたO(1)ですが、サイズ変更が悪いタイミングで発生しないように注意してください。挿入はO(n)で、右側のすべてのアイテムを移動する必要があります。Remove is O(n)右側のすべてのアイテムを移動する必要があります。

リストはリンクリストではないことにも注意してください。これは、型指定されたArrayListです。リストのドキュメントには、ほとんどの場合にパフォーマンスが向上することが記載されていますが、その理由は示されていません。

最善の方法は、問題に適したデータ構造を選択することです。これは多くのことに依存するため、 System.Collections.Generic名前空間を参照することをお勧めします。

この特定のケースでは、適切なキー値を考え出すことができれば、辞書が最善の策であると言えます。O(1)に近づくインサートとリムーブがあります。ただし、ディクショナリを使用する場合でも、内部配列のサイズを変更しないように注意する必要があります(O(n)操作)。コンストラクターで使用する予定のより大きな初期容量を指定することにより、それらに多くのスペースを与えるのが最善です。

-リック

于 2008-09-16T20:16:17.163 に答える
2

ArrayList と List は、必要に応じて配列を 1 つ以上大きくします (サイズを 2 倍にすることによるものだと思いますが、ソースは確認していません)。動的にサイズ変更された配列を作成する場合は、通常、これらが最適な選択です。

ベンチマークが、配列のサイズ変更がアプリケーションの速度を著しく低下させていることを示している場合 (覚えておいてください - 時期尚早の最適化は諸悪の根源です)、サイズ変更動作を微調整したカスタム配列クラスの作成を評価できます。

于 2008-09-16T19:29:24.250 に答える
2

配列のサイズが変更されると、新しい配列を割り当てて、内容をコピーする必要があります。配列の内容のみを変更する場合、それは単なるメモリの割り当てです。

したがって、配列のサイズがわからない場合、またはサイズが変更される可能性がある場合は、配列を使用しないでください。ただし、固定長の配列がある場合は、インデックスで要素を取得する簡単な方法です。

于 2008-09-16T19:26:56.550 に答える
1

多くの追加を行う予定であり、ランダムアクセス(などmyArray[i])を行わない場合。リンクリスト( )は、実装LinkedList<T>のように「成長」する必要がないため、使用を検討できます。List<T>ただし、実際にアクセスできるのは、インターフェースLinkedList<T>を使用する実装内のアイテムのみであることに注意してください。IEnumerable<T>

于 2008-09-16T20:21:07.730 に答える
1

最善の方法は、可能であれば事前に必要なだけのメモリを割り当てることです。これにより、.NETがヒープ上のメモリを取得するために追加の呼び出しを行う必要がなくなります。それができない場合は、5 つまたはアプリケーションにとって意味のある数のチャンクで割り当てるのが理にかなっています。

これは、実際に何にでも適用できるルールです。

于 2008-09-16T19:27:47.453 に答える
1

標準配列は、連続するブロックで必要なすべてのメモリを予約する長さで定義する必要があります。配列にアイテムを追加すると、すでに予約されているメモリのブロック内に配置されます。

于 2008-09-16T19:28:02.333 に答える
1

配列は、少数の書き込みと多数の読み取り、特に反復的な性質の場合に最適です。それ以外の場合は、他の多くのデータ構造のいずれかを使用します。

于 2008-09-16T19:29:33.797 に答える
1

コレクションの存続期間中にアイテムをコレクションに追加するつもりなら、List を使用します。宣言されたときのコレクションのサイズが確実にわかっている場合は、配列を使用します。

List に対して配列を一般的に使用するのは、コレクションをオブジェクトのプロパティとして返す必要がある場合です。呼び出し元が List の Add メソッドを介してそのコレクションに項目を追加するのではなく、コレクションに項目を追加するようにします。私のオブジェクトのインターフェースを介して。その場合、内部の List を取得して ToArray を呼び出し、配列を返します。

于 2008-09-16T20:07:13.987 に答える
1

配列は検索に最適です。ただし、配列のサイズの変更にはコストがかかります。

配列のサイズを変更するシナリオでは、増分サイズ調整をサポートするコンテナーを使用する必要があります。初期サイズを設定できる ArrayList を使用できます。サイズと容量を継続的にチェックし、容量を大きなチャンクで増やしてサイズ変更の数を制限できます。

または、リンクされたリストを使用することもできます。しかし、ルックアップは遅いです...

于 2008-09-16T19:33:56.627 に答える