C#で2つ以上のバイト配列を組み合わせる最良の方法


238

C#に3バイトの配列があり、1つに結合する必要があります。このタスクを完了するための最も効率的な方法は何ですか?


3
具体的にあなたの要件は何ですか?配列の結合をとっていますか、それとも同じ値の複数のインスタンスを保持していますか?アイテムを並べ替えますか、それとも初期配列の順序を保持しますか?速度またはコード行の効率を探していますか?
ジェイソン

「最高」はあなたの要件が何であるかに依存します。
アディ

7
あなたはLINQを使用することができます場合は、あなただけ使用することができますConcat:方法IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne

1
質問がより明確になるようにしてください。このあいまいな質問は、時間をかけてあなたに答えるのに十分なほど良い人々の間で多くの混乱を引き起こしました。
Drew Noakes、

回答:


326

プリミティブ型(バイトを含む)の場合は、のSystem.Buffer.BlockCopy代わりに使用しますSystem.Array.Copy。より速いです。

推奨される各メソッドを、それぞれ10バイトの3つの配列を使用して100万回実行されるループで計時しました。結果は次のとおりです。

  1. 新しいバイト配列を使用System.Array.Copy -0.2187556秒
  2. 新しいバイト配列を使用System.Buffer.BlockCopy -0.1406286秒
  3. IEnumerable <byte>(C#降伏演算子を使用)-0.0781270秒
  4. LINQのConcat <>を使用したIEnumerable <byte>-0.0781270秒

各配列のサイズを100要素に増やし、テストを再実行しました。

  1. 新しいバイト配列を使用System.Array.Copy -0.2812554秒
  2. 新しいバイト配列を使用System.Buffer.BlockCopy -0.2500048秒
  3. IEnumerable <byte>(C#生成演算子を使用)-0.0625012秒
  4. LINQのConcat <>を使用したIEnumerable <byte>-0.0781265秒

各配列のサイズを1000要素に増やし、テストを再実行しました。

  1. 新しいバイト配列を使用System.Array.Copy -1.0781457秒
  2. 新しいバイト配列を使用System.Buffer.BlockCopy -1.0156445秒
  3. IEnumerable <byte>(C#生成演算子を使用)-0.0625012秒
  4. LINQのConcat <>を使用したIEnumerable <byte>-0.0781265秒

最後に、各配列のサイズを100万要素に増やし、テストを再実行して、各ループを4000回だけ実行しました。

  1. 新しいバイト配列を使用System.Array.Copy -13.4533833秒
  2. 新しいバイト配列を使用System.Buffer.BlockCopy -13.1096267秒
  3. IEnumerable <byte>(C#のyield演算子を使用)-0秒
  4. LINQのConcat <>を使用したIEnumerable <byte>-0秒

したがって、新しいバイト配列が必要な場合は、

byte[] rv = new byte[a1.Length + a2.Length + a3.Length];
System.Buffer.BlockCopy(a1, 0, rv, 0, a1.Length);
System.Buffer.BlockCopy(a2, 0, rv, a1.Length, a2.Length);
System.Buffer.BlockCopy(a3, 0, rv, a1.Length + a2.Length, a3.Length);

ただし、を使用できる場合IEnumerable<byte>DEFINITELYは LINQのConcat <>メソッドを優先します。C#のyield演算子より少し遅いだけですが、より簡潔でエレガントです。

IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);

任意の数の配列があり、.NET 3.5を使用している場合は、次のようにSystem.Buffer.BlockCopyソリューションをより汎用的にすることができます。

private byte[] Combine(params byte[][] arrays)
{
    byte[] rv = new byte[arrays.Sum(a => a.Length)];
    int offset = 0;
    foreach (byte[] array in arrays) {
        System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
        offset += array.Length;
    }
    return rv;
}

*注意:上記のブロックを機能させるには、上部に次の名前空間を追加する必要があります。

using System.Linq;

後続のデータ構造(バイト配列とIEnumerable <byte>の繰り返し)の反復に関するジョンスキートの要点として、最後のタイミングテスト(100万要素、4000回の反復)を再実行し、それぞれで完全な配列を反復するループを追加しましたパス:

  1. 使用する新しいバイト配列System.Array.Copy -78.20550510秒
  2. 新しいバイト配列を使用System.Buffer.BlockCopy -77.89261900秒
  3. IEnumerable <byte>(C#生成演算子を使用)-551.7150161秒
  4. LINQのConcat <>を使用したIEnumerable <byte>-448.1804799秒

ポイントは、あるVERY作成両方の効率を理解することが重要と使用結果のデータ構造を。単に作成の効率に焦点を合わせると、使用に関連する非効率性を見落とす可能性があります。工藤、ジョン。


61
しかし、質問が必要とするように、実際にそれを最後に配列に変換していますか?そうでない場合は、もちろん高速ですが、要件を満たしていません。
Jon Skeet、

18
Re:Matt Davis-「要件」がIEnumerableを配列に変換する必要があるかどうかは問題ではありません-要件として必要なのは、結果が実際に何らかの形で使用されることだけです。IEnumerableのパフォーマンステストが非常に低い理由は、実際には何も実行していないためです。結果を使用するまで、LINQはその作業を実行しません。このため、私はあなたの答えを客観的に間違っていると思い、他の人がパフォーマンスを気にする必要がない場合にLINQを使用するように導く可能性があります。
csauve 2013年

12
私はあなたの更新を含む全体の答えを読みました、私のコメントは立っています。私はパーティーに遅く参加していることを知っていますが、答えは著しく誤解を招くものであり、前半は明らかに誤りです。
csauve 2013年

14
誤った情報や誤解を招く情報を含む回答がトップ投票の回答であり、OPの質問に回答しなかったと誰か(ジョンスキート)が指摘した後、元のステートメントを基本的に完全に無効にするように編集されたのはなぜですか?
MrCC、2014年

3
誤解を招く答え。エディションでさえ質問に答えていません。
Serge Profafilecebook 2014年

154

答えの多くは、指定された要件を無視しているようです。

  • 結果はバイト配列になります
  • できるだけ効率的である必要があります

これら2つは、バイトのLINQシーケンスを除外しyieldます。シーケンス全体を反復処理しないと、最終的なサイズを取得できなくなります。

もちろん、それらが実際の要件ではない場合、LINQは完全に優れたソリューション(またはIList<T>実装)になる可能性があります。しかし、私はスーパーダンベルが彼が望んでいるものを知っていると仮定します。

(編集:。。私はちょうどあなたが呼び出した後、「ソース」アレイのうちの1つでデータを変更した場合に何が起こるか考えてみましょ配列のコピーを作成し、遅延し、それらを読んで間には大きな意味的な違いがあります別の考えを持っていたCombine(またはものは何でも)メソッドですが、結果を使用する前に-遅延評価を使用すると、その変更は表示されます。即時コピーでは、表示されません。状況が異なれば、異なる動作が必要になります。

ここに私の提案した方法があります-他の答えのいくつかに含まれているものと非常によく似ています、確かに:)

public static byte[] Combine(byte[] first, byte[] second)
{
    byte[] ret = new byte[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

public static byte[] Combine(byte[] first, byte[] second, byte[] third)
{
    byte[] ret = new byte[first.Length + second.Length + third.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    Buffer.BlockCopy(third, 0, ret, first.Length + second.Length,
                     third.Length);
    return ret;
}

public static byte[] Combine(params byte[][] arrays)
{
    byte[] ret = new byte[arrays.Sum(x => x.Length)];
    int offset = 0;
    foreach (byte[] data in arrays)
    {
        Buffer.BlockCopy(data, 0, ret, offset, data.Length);
        offset += data.Length;
    }
    return ret;
}

もちろん、「params」バージョンでは、バイト配列の配列を最初に作成する必要があるため、余分な非効率性が生じます。


ジョン、私はあなたの言っていることを正確に理解しています。私の唯一のポイントは、他の解決策が存在することに気付かずに、特定の実装をすでに念頭に置いて質問されることがあるということです。選択肢を提供せずに単に答えを提供することは、私にとって不快なようです。考え?
マットデイビス

1
@マット:はい、代替案を提供することは良いことです、質問された質問への回答としてそれらを渡すのではなく、代替案であること説明する価値があります。(私はあなたがそれをしたと言っているのではありません-あなたの答えはとても良いです。)
Jon Skeet

4
(私はあなたのパフォーマンスベンチマークが遅延評価に不当な利点を与えないようにするために、それぞれの場合にもすべての結果を通過するのにかかる時間を示すべきだと思います。)
Jon Skeet 09/6

1
「結果は配列でなければならない」という要件を満たしていない場合でも、「結果は何らかの形で使用する必要がある」という要件を満たすだけでは、LINQは最適ではなくなります。結果を使用できるようにするための要件は暗黙のうちにあると思います!
csauve 2013年

2
@andleer:それ以外は、Buffer.BlockCopyはプリミティブ型でのみ機能します。
Jon Skeet 14年

44

MattのLINQの例を、コードをクリーンにするためにさらに一歩進めました。

byte[] rv = a1.Concat(a2).Concat(a3).ToArray();

私の場合、配列は小さいので、パフォーマンスについては気にしません。


3
短くてシンプルな解決策、パフォーマンステストはすばらしいでしょう。
セバスチャン

3
これは明確で読みやすく、外部のライブラリやヘルパーを必要とせず、開発時間の面では非常に効率的です。実行時のパフォーマンスが重要ではない場合に最適です。
binki 2018年

28

単に新しいバイト配列が必要な場合は、以下を使用します。

byte[] Combine(byte[] a1, byte[] a2, byte[] a3)
{
    byte[] ret = new byte[a1.Length + a2.Length + a3.Length];
    Array.Copy(a1, 0, ret, 0, a1.Length);
    Array.Copy(a2, 0, ret, a1.Length, a2.Length);
    Array.Copy(a3, 0, ret, a1.Length + a2.Length, a3.Length);
    return ret;
}

または、IEnumerableが1つだけ必要な場合は、C#2.0のyield演算子の使用を検討してください。

IEnumerable<byte> Combine(byte[] a1, byte[] a2, byte[] a3)
{
    foreach (byte b in a1)
        yield return b;
    foreach (byte b in a2)
        yield return b;
    foreach (byte b in a3)
        yield return b;
}

大きなストリームをマージするための2番目のオプションと同様のことを行いました。:)
グレッグ・D

2
2番目のオプションは素晴らしいです。+1。
R.マルティーニョフェルナンデス

10

私は実際にConcatを使用していくつかの問題に遭遇しました...(1000万の配列では、実際にクラッシュしました)。

次のコードはシンプルで簡単で、クラッシュすることなく十分に機能することがわかりました。これは、任意の数の配列(3つだけでなく)でも機能します(LINQを使用しています)。

public static byte[] ConcatByteArrays(params byte[][]  arrays)
{
    return arrays.SelectMany(x => x).ToArray();
}

6

memorystreamクラスは、私にとってこの仕事をかなりうまく行います。バッファクラスをメモリストリームほど高速に実行できませんでした。

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}

3
qweが述べたように、私はループで10,000,000回テストを行い、MemoryStreamはBuffer.BlockCopyより290%遅い
esac

場合によっては、個々の配列の長さをまったく知らなくても、列挙可能な配列を反復処理することがあります。これはこのシナリオでうまく機能します。BlockCopyは宛先アレイが事前に作成されていることを前提としています
Sentinel

@Sentinelが言ったように、私が書く必要があるもののサイズがわからないので、この答えは私にぴったりです。また、.NET Core 3の[ReadOnly] Span <byte>にも対応しています。

サイズの最終サイズでMemoryStreamを初期化すると、再作成されず、@ esacが高速になります。
遠野ナム

2
    public static bool MyConcat<T>(ref T[] base_arr, ref T[] add_arr)
    {
        try
        {
            int base_size = base_arr.Length;
            int size_T = System.Runtime.InteropServices.Marshal.SizeOf(base_arr[0]);
            Array.Resize(ref base_arr, base_size + add_arr.Length);
            Buffer.BlockCopy(add_arr, 0, base_arr, base_size * size_T, add_arr.Length * size_T);
        }
        catch (IndexOutOfRangeException ioor)
        {
            MessageBox.Show(ioor.Message);
            return false;
        }
        return true;
    }

残念ながら、これはすべてのタイプで機能するわけではありません。Marshal.SizeOf()は多くのタイプのサイズを返すことができなくなります(このメソッドを文字列の配列で使用すると、「タイプ 'System.String'はアンマネージ構造としてマーシャリングできません。意味のあるサイズがないか、オフセットを計算できます。 "を追加することにより、タイプパラメータを参照タイプのみに制限することもできますwhere T : structが、CLRの内部の専門家ではないため、特定の構造体でも例外が発生するかどうかはわかりません(たとえば、参照型フィールドが含まれている場合)
Daniel Scott

2
    public static byte[] Concat(params byte[][] arrays) {
        using (var mem = new MemoryStream(arrays.Sum(a => a.Length))) {
            foreach (var array in arrays) {
                mem.Write(array, 0, array.Length);
            }
            return mem.ToArray();
        }
    }

このコードサンプルで何が行われるかについての簡単な説明を投稿した方がよいでしょう。
AFract

1
バイト配列の配列を1つの大きなバイト配列に連結します(このように):[1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 、6,7]
Peter Ertl 2014

1

ジェネリックを使用して配列を組み合わせることができます。次のコードは、3つの配列に簡単に拡張できます。これにより、異なるタイプの配列に対してコードを複製する必要がなくなります。上記の回答のいくつかは、私には過度に複雑に見えます。

private static T[] CombineTwoArrays<T>(T[] a1, T[] a2)
    {
        T[] arrayCombined = new T[a1.Length + a2.Length];
        Array.Copy(a1, 0, arrayCombined, 0, a1.Length);
        Array.Copy(a2, 0, arrayCombined, a1.Length, a2.Length);
        return arrayCombined;
    }

0

@Jon Skeetによって提供された回答の一般化を以下に示します。基本的には同じですが、バイトだけでなく、あらゆるタイプの配列で使用できます。

public static T[] Combine<T>(T[] first, T[] second)
{
    T[] ret = new T[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

public static T[] Combine<T>(T[] first, T[] second, T[] third)
{
    T[] ret = new T[first.Length + second.Length + third.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    Buffer.BlockCopy(third, 0, ret, first.Length + second.Length,
                     third.Length);
    return ret;
}

public static T[] Combine<T>(params T[][] arrays)
{
    T[] ret = new T[arrays.Sum(x => x.Length)];
    int offset = 0;
    foreach (T[] data in arrays)
    {
        Buffer.BlockCopy(data, 0, ret, offset, data.Length);
        offset += data.Length;
    }
    return ret;
}

3
危険!これらのメソッドは、1バイトより長い要素を持つ配列型のプロパティでは機能しません(バイト配列以外のほとんどすべて)。Buffer.BlockCopy()は、配列要素の数ではなく、バイト数で機能します。バイト配列で簡単に使用できるのは、配列のすべての要素が1バイトであるため、配列の物理的な長さが要素の数と等しいためです。Johnのbyte []メソッドをジェネリックメソッドに変換するには、すべてのオフセットと長さを単一の配列要素のバイト長で乗算する必要があります。そうしないと、すべてのデータがコピーされません。
ダニエルスコット

2
通常、これを機能させるには、使用する単一の要素のサイズを計算し、sizeof(...)それにコピーする要素の数を掛けますが、sizeofはジェネリック型では使用できません。一部のタイプではを使用Marshal.SizeOf(typeof(T))できますが、特定のタイプ(文字列など)でランタイムエラーが発生します。CLR型の内部動作について十分な知識がある人は、ここで可能なすべてのトラップを指摘できます。[BlockCopyを使用して]汎用配列連結メソッドを作成するのは簡単なことではないと言えます。
ダニエルスコット

2
そして最後に-代わりにArray.Copyを使用することで、上記の一般的な配列連結メソッドを上に示した方法とほぼ同じ方法で(わずかに低いパフォーマンスで)書くことができます。すべてのBuffer.BlockCopy呼び出しをArray.Copy呼び出しに置き換えるだけです。
ダニエルスコット

0
    /// <summary>
    /// Combine two Arrays with offset and count
    /// </summary>
    /// <param name="src1"></param>
    /// <param name="offset1"></param>
    /// <param name="count1"></param>
    /// <param name="src2"></param>
    /// <param name="offset2"></param>
    /// <param name="count2"></param>
    /// <returns></returns>
    public static T[] Combine<T>(this T[] src1, int offset1, int count1, T[] src2, int offset2, int count2) 
        => Enumerable.Range(0, count1 + count2).Select(a => (a < count1) ? src1[offset1 + a] : src2[offset2 + a - count1]).ToArray();

寄付ありがとうございます。10年以上前から、これに対する高い評価の回答がすでにいくつかあるので、アプローチの特徴を説明することは有用です。なぜ誰かが例えば受け入れられた答えの代わりにこれを使うべきなのでしょうか?
Jeremy Caney

理解すべき明確なコードがあるので、拡張メソッドの使用が好きです。このコードは、開始インデックスとカウントおよび連結を持つ2つの配列を選択します。このメソッドも拡張されました。つまり、これはいつでも準備が整っているすべてのアレイタイプに
当てはまります

それは私には理にかなっています!質問を編集して、その情報を含めてもよろしいですか。それを前もって持っていることは将来の読者にとって価値があると私は思うので、彼らはあなたのアプローチを既存の答えから素早く区別することができます。ありがとうございました!
Jeremy Caney

-1

バイト配列のリストを渡すために必要なものはすべて、この関数はバイトの配列(マージ済み)を返します。これは私が考える最良の解決策です:)。

public static byte[] CombineMultipleByteArrays(List<byte[]> lstByteArray)
        {
            using (var ms = new MemoryStream())
            {
                using (var doc = new iTextSharp.text.Document())
                {
                    using (var copy = new PdfSmartCopy(doc, ms))
                    {
                        doc.Open();
                        foreach (var p in lstByteArray)
                        {
                            using (var reader = new PdfReader(p))
                            {
                                copy.AddDocument(reader);
                            }
                        }

                        doc.Close();
                    }
                }
                return ms.ToArray();
            }
        }

-5

Concatが正解ですが、何らかの理由で、手巻きのものが最も多くの票を得ています。もしあなたがその答えが好きなら、おそらくあなたはこのより一般的な解決策をもっともっと欲しいでしょう:

    IEnumerable<byte> Combine(params byte[][] arrays)
    {
        foreach (byte[] a in arrays)
            foreach (byte b in a)
                yield return b;
    }

次のようなことができます:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();

5
質問は、特に最も効率的なソリューションを求めています。Enumerable.ToArrayは、最初の最終的な配列のサイズがわからないため、あまり効率的ではありません。一方、手巻きの手法では可能です。
Jon Skeet、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.