34

C#で入力byte[] valuesされている場合、値(byte)0x00を配列の前に追加します。これには、新しい配列を作成し、古い配列の内容を追加する必要があると思います。速度は私のアプリケーションの重要な側面です。これを行うための最良の方法は何ですか?

- 編集 -

これbyte[]は、DSA(デジタル署名アルゴリズム)パラメーターを格納するために使用されます。操作はアレイごとに1回だけ実行する必要がありますが、この操作は多くの異なるで実行される可能性があるため、速度は重要ですbyte[]

4

8 に答える 8

47

この操作を1回だけ実行する場合、選択肢はそれほど多くありません。モンローの答えによって提供されたコードはうまくいくはずです。

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values

ただし、この操作を複数回実行する場合は、さらにいくつかの選択肢があります。配列にデータを付加することは効率的な操作ではないという根本的な問題があるため、別のデータ構造を使用することを選択できます。

ALinkedListはデータを効率的に追加できますが、メモリの割り当て/割り当て解除が多くなり、メモリの局所性が失われるため、ほとんどのタスクでは一般的に効率が低くなり、正味の勝利にはならない可能性があります。

両端キュー(dequeと呼ばれる)は、すばらしいデータ構造になります。開始または終了に効率的に追加し、構造内のどこにでも効率的にデータにアクセスできます(ただし、開始または終了以外の場所に効率的に挿入することはできません)。ここでの主な問題は、.NETがdequeの実装を提供しないことです。実装されたサードパーティのライブラリを見つける必要があります。

また、「追加する必要のあるデータ」を追跡し(リスト/キューなどを使用)、実際にデータをできるだけ長く追加するのを待つことで、コピー時に大幅に節約できるため、作成を最小限に抑えることができます。可能な限り新しい配列の数を増やし、既存の要素のコピー数を制限します。

また、構造を調整して、最初ではなく最後に追加するようにすることもできます(後で逆にする必要があることがわかっている場合でも)。短時間で大量に追加する場合は、データをに保存してList(効率的に最後に追加できます)、最後に追加する価値があるかもしれません。必要に応じて、リストのラッパーであり、逆になっているという事実を隠すクラスを作成する価値がある場合もあります。内部が実際にデータを後方に保持している場合でも、データが正常に格納されているかのように、外部から表示されるように、などにマップiするインデクサーを作成できます。Count-iList

于 2012-07-09T20:29:35.877 に答える
20

さて、この質問に関するパフォーマンスの問題を見てみましょう。これは答えではなく、どちらのオプションがより効率的かを確認するためのマイクロベンチマークにすぎません。

それでは、シナリオを設定しましょう。

  • ランダムに入力された1,000,000アイテムのバイト配列
  • アイテム0x00を追加する必要があります

3つのオプションがあります。

  1. 新しいアレイを手動で作成して移入する
  2. 新しい配列を手動で作成し、Array.Copy(@Monroe)を使用する
  3. リストの作成、配列の読み込み、アイテムの挿入、リストの配列への変換

コードは次のとおりです。

    byte[] byteArray = new byte[1000000];

    for (int i = 0; i < byteArray.Length; i++)
    {
        byteArray[i] = Convert.ToByte(DateTime.Now.Second);
    }

    Stopwatch stopWatch = new Stopwatch();

    //#1 Manually creating and populating a new array;

    stopWatch.Start();

    byte[] extendedByteArray1 = new byte[byteArray.Length + 1];

    extendedByteArray1[0] = 0x00;

    for (int i = 0; i < byteArray.Length; i++)
    {
        extendedByteArray1[i + 1] = byteArray[i];
    }

    stopWatch.Stop();
    Console.WriteLine(string.Format("#1: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    //#2 Using a new array and Array.Copy

    stopWatch.Start();

    byte[] extendedByteArray2 = new byte[byteArray.Length + 1];
    extendedByteArray2[0] = 0x00;                                
    Array.Copy(byteArray, 0, extendedByteArray2, 1, byteArray.Length);

    stopWatch.Stop();
    Console.WriteLine(string.Format("#2: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    //#3 Using a List

    stopWatch.Start();

    List<byte> byteList = new List<byte>();
    byteList.AddRange(byteArray);
    byteList.Insert(0, 0x00);

    byte[] extendedByteArray3 = byteList.ToArray();

    stopWatch.Stop();
    Console.WriteLine(string.Format("#3: {0} ms", stopWatch.ElapsedMilliseconds));
    stopWatch.Reset();

    Console.ReadLine();

そして結果は次のとおりです。

#1: 9 ms
#2: 1 ms
#3: 6 ms

私はそれを複数回実行し、異なる数を取得しましたが、比率は常に同じです:#2は常に最も効率的な選択です。

私の結論:配列はリストよりも効率的であり(機能は劣りますが)、どういうわけかArray.Copy実際に最適化されています(ただし、それを理解したいと思います)。

フィードバックをいただければ幸いです。

よろしくお願いします。

PS:これは剣闘士の投稿ではありません。私たちはQ&Aサイトで学び、教えています。そして学ぶ

于 2012-07-09T20:47:59.813 に答える
15

ご想像のとおり、これを行う最も速い方法は、長さ+ 1の新しい配列を作成し、古い値をすべてコピーすることです。

これを何度も行う場合は、List<byte>代わりにを使用することをお勧めします。これbyte[]は、基盤となるストレージを拡張しながら再割り当ておよびコピーするコストがより効果的に償却されるためです。通常の場合、の基になるベクトルは、現在の容量を超えるList追加または挿入が行われるたびに2倍になります。List

...

byte[] newValues = new byte[values.Length + 1];
newValues[0] = 0x00;                                // set the prepended value
Array.Copy(values, 0, newValues, 1, values.Length); // copy the old values
于 2012-07-09T20:08:51.820 に答える
9

.NET 4.7.1以降の最も簡単でクリーンな方法は、副作用のないを使用することPrepend()です。

シーケンスの先頭に値を追加します。

// Creating an array of numbers
var numbers = new[] { 1, 2, 3 };

// Trying to prepend any value of the same type
var results = numbers.Prepend(0);

// output is 0, 1, 2, 3
Console.WriteLine(string.Join(", ", results ));
于 2020-01-08T10:04:35.033 に答える
3

データを頻繁に追加する必要があるが、個々の要素へのO(1)ランダムアクセスも必要な場合は、新しい値をすばやく追加するために、ある程度のパディングによって過剰に割り当てられた配列を使用します。これは、array.lengthが長さ+パディングを示すため、実際のコンテンツの長さを別の変数に格納する必要があることを意味します。パディングの1つのスロットを使用して新しい値が追加されます。パディングがなくなるまで、割り当てとコピーは必要ありません。事実上、配分はいくつかの追加操作で償却されます。これらのデータ構造の多くがあるかのように、プログラムで一度にかなりの量のパディングを使用できるように、速度とスペースのトレードオフがあります。

これと同じ手法を前置に使用できます。追加の場合と同様に、ユーザーと実装の間にインターフェースまたは抽象化を導入できます。新しいメモリ割り当てがたまにしか必要ないように、パディングのスロットをいくつか持つことができます。上記で示唆したように、インデックスを逆にする追加データ構造を使用して、追加インターフェイスを実装することもできます。

データ構造を一般的なコレクションインターフェイスの実装としてパッケージ化して、インターフェイスがユーザーにとってかなり正常に見えるようにします(配列リストなど)。

(また、削除がサポートされている場合は、gcの負荷を減らすために、削除されたらすぐに要素をクリアすると便利です。)

重要な点は、実装とインターフェイスを別々に検討することです。これらを分離すると、さまざまな実装を選択したり、最小限のインターフェイスを使用して実装の詳細を非表示にしたりする柔軟性が得られます。

ドメインへの適用性に応じて、使用できるデータ構造は他にもたくさんあります。ロープまたはギャップバッファ; メモ帳のようなエディタを実装するのに適した最適なデータ構造は何ですか?を参照してください。; Trieもいくつかの便利なことをします。

于 2012-07-09T21:25:01.570 に答える
3

これは非常に古い投稿であることは知っていますが、実際にはラムダを使用するのが好きです。確かに私のコードは最も効率的な方法ではないかもしれませんが、その読み取り可能で1行です。.ConcatとArraySegmentを組み合わせて使用​​しています。

 string[] originalStringArray = new string[] { "1", "2", "3", "5", "6" };
 int firstElementZero = 0;
 int insertAtPositionZeroBased = 3;
 string stringToPrepend = "0";
 string stringToInsert = "FOUR"; // Deliberate !!!

 originalStringArray = new string[] { stringToPrepend }
            .Concat(originalStringArray).ToArray();

 insertAtPositionZeroBased += 1; // BECAUSE we prepended !!
 originalStringArray = new ArraySegment<string>(originalStringArray, firstElementZero, insertAtPositionZeroBased)       
                .Concat(new string[] { stringToInsert })
                .Concat(new ArraySegment<string>(originalStringArray, insertAtPositionZeroBased, originalStringArray.Length - insertAtPositionZeroBased)).ToArray();
于 2017-02-08T16:15:22.247 に答える
2

最良の選択は、後でこのコレクションで何をするかによって異なります。これまでに行われる長さを変更する編集がこれだけである場合、最善の策は、スロットを1つ追加して新しいアレイを作成Array.Copy()し、残りを使用することです。新しいC#配列は常にゼロになるため、最初の値を初期化する必要はありません。

byte[] PrependWithZero(byte[] input)
{
    var newArray = new byte[input.Length + 1];
    Array.Copy(input, 0, newArray, 1, input.Length);
    return newArray;
}

他の長さを変更する編集が行われる可能性がある場合List<byte>、追加が常に最初に行われるとは限らない限り、最もパフォーマンスの高いオプションは、すべてを使用することです。(その場合、リンクリストでさえ、手に負えない形で却下できるオプションではない可能性があります。):

var list = new List<byte>(input);
list.Insert(0, 0);
于 2012-07-09T20:14:01.197 に答える
0

これは4年以上前に受け入れられた投稿であることを認識していますが、これが関連する可能性がある人にとっては、Buffer.BlockCopyの方が高速です。

于 2016-02-26T09:25:30.557 に答える