C#に3バイトの配列があり、1つに結合する必要があります。このタスクを完了するための最も効率的な方法は何ですか?
C#に3バイトの配列があり、1つに結合する必要があります。このタスクを完了するための最も効率的な方法は何ですか?
回答:
プリミティブ型(バイトを含む)の場合は、のSystem.Buffer.BlockCopy
代わりに使用しますSystem.Array.Copy
。より速いです。
推奨される各メソッドを、それぞれ10バイトの3つの配列を使用して100万回実行されるループで計時しました。結果は次のとおりです。
System.Array.Copy
-0.2187556秒System.Buffer.BlockCopy
-0.1406286秒各配列のサイズを100要素に増やし、テストを再実行しました。
System.Array.Copy
-0.2812554秒System.Buffer.BlockCopy
-0.2500048秒各配列のサイズを1000要素に増やし、テストを再実行しました。
System.Array.Copy
-1.0781457秒System.Buffer.BlockCopy
-1.0156445秒最後に、各配列のサイズを100万要素に増やし、テストを再実行して、各ループを4000回だけ実行しました。
System.Array.Copy
-13.4533833秒System.Buffer.BlockCopy
-13.1096267秒したがって、新しいバイト配列が必要な場合は、
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回の反復)を再実行し、それぞれで完全な配列を反復するループを追加しましたパス:
System.Array.Copy
-78.20550510秒System.Buffer.BlockCopy
-77.89261900秒ポイントは、あるVERY作成両方の効率を理解することが重要と使用結果のデータ構造を。単に作成の効率に焦点を合わせると、使用に関連する非効率性を見落とす可能性があります。工藤、ジョン。
答えの多くは、指定された要件を無視しているようです。
これら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」バージョンでは、バイト配列の配列を最初に作成する必要があるため、余分な非効率性が生じます。
MattのLINQの例を、コードをクリーンにするためにさらに一歩進めました。
byte[] rv = a1.Concat(a2).Concat(a3).ToArray();
私の場合、配列は小さいので、パフォーマンスについては気にしません。
単に新しいバイト配列が必要な場合は、以下を使用します。
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;
}
memorystreamクラスは、私にとってこの仕事をかなりうまく行います。バッファクラスをメモリストリームほど高速に実行できませんでした。
using (MemoryStream ms = new MemoryStream())
{
ms.Write(BitConverter.GetBytes(22),0,4);
ms.Write(BitConverter.GetBytes(44),0,4);
ms.ToArray();
}
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;
}
where T : struct
が、CLRの内部の専門家ではないため、特定の構造体でも例外が発生するかどうかはわかりません(たとえば、参照型フィールドが含まれている場合)
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();
}
}
ジェネリックを使用して配列を組み合わせることができます。次のコードは、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;
}
@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;
}
sizeof(...)
それにコピーする要素の数を掛けますが、sizeofはジェネリック型では使用できません。一部のタイプではを使用Marshal.SizeOf(typeof(T))
できますが、特定のタイプ(文字列など)でランタイムエラーが発生します。CLR型の内部動作について十分な知識がある人は、ここで可能なすべてのトラップを指摘できます。[BlockCopyを使用して]汎用配列連結メソッドを作成するのは簡単なことではないと言えます。
/// <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();
バイト配列のリストを渡すために必要なものはすべて、この関数はバイトの配列(マージ済み)を返します。これは私が考える最良の解決策です:)。
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();
}
}
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();