У меня есть 3 байтовых массива в C #, которые мне нужно объединить в один. Какой будет наиболее эффективный метод для выполнения этой задачи?
У меня есть 3 байтовых массива в C #, которые мне нужно объединить в один. Какой будет наиболее эффективный метод для выполнения этой задачи?
Ответы:
Для примитивных типов (включая байты) используйте System.Buffer.BlockCopyвместо System.Array.Copy. Это быстрее
Я рассчитал каждый из предложенных методов в цикле, выполняемом 1 миллион раз, используя 3 массива по 10 байт каждый. Вот результаты:
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 секундНаконец, я увеличил размер каждого массива до 1 миллиона элементов и перезапустил тест, выполнив каждый цикл только 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>, ОБЯЗАТЕЛЬНО предпочитают 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>) я перезапустил последний временной тест (1 миллион элементов, 4000 итераций), добавив цикл, который повторяется по всему массиву с каждым проходить:
System.Array.Copy - 78,20550510 секундSystem.Buffer.BlockCopy байтового массива - 77,89261900 секундДело в том, что ОЧЕНЬ важно понимать эффективность как создания, так и использования полученной структуры данных. Простое сосредоточение внимания на эффективности создания может упускать из виду неэффективность, связанную с использованием. Слава, Джон.
Мне кажется, что многие ответы игнорируют заявленные требования:
Вместе они исключают последовательность байтов LINQ - все, что с ними yield, сделает невозможным получение окончательного размера без итерации всей последовательности.
Если это, конечно, не реальные требования, LINQ может быть отличным решением (или IList<T>реализацией). Тем не менее, я предполагаю, что Superdumbell знает, что он хочет.
(РЕДАКТИРОВАТЬ: Я только что подумал. Существует большая семантическая разница между копированием массивов и их ленивым чтением. Подумайте, что произойдет, если вы измените данные в одном из «исходных» массивов после вызова 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» требует сначала создания массива байтовых массивов, что вносит дополнительную неэффективность.
Я взял пример 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, рассмотрите возможность использования оператора yield C # 2.0:
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;
}
На самом деле я столкнулся с некоторыми проблемами при использовании Concat ... (с массивами в 10 миллионов он действительно потерпел крах).
Я обнаружил, что следующее простое, легкое и работает достаточно хорошо, без сбоев, и работает для ЛЮБОГО числа массивов (не только для трех) (используется LINQ):
public static byte[] ConcatByteArrays(params byte[][] arrays)
{
return arrays.SelectMany(x => x).ToArray();
}
Класс памяти потока делает эту работу очень хорошо для меня. Я не мог заставить буферный класс работать так же быстро, как поток памяти.
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();
}
}
Можно использовать дженерики для объединения массивов. Следующий код может быть легко расширен до трех массивов. Таким образом, вам никогда не нужно дублировать код для различных типов массивов. Некоторые из приведенных выше ответов кажутся мне слишком сложными.
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();