У мене є 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>
, НАДАГАЛЬНО віддайте перевагу методу Concat <> LINQ. Це лише трохи повільніше, ніж оператор виходу C #, але є більш стислим та елегантним.
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 <байт>), я повторно провів останній тест хронометражу (1 мільйон елементів, 4000 ітерацій), додавши цикл, який повторює повний масив з кожним пропуск:
System.Array.Copy
- 78.20550510 секундSystem.Buffer.BlockCopy
нового байтового масиву - 77,89261900 секундСправа в тому, що ДУЖЕ важливо розуміти ефективність як створення, так і використання отриманої структури даних. Просто зосередження уваги на ефективності створення може не помітити неефективність, пов'язану з використанням. Кудос, Джон.
Багато відповідей, як мені здається, ігнорують заявлені вимоги:
Ці два разом виключають послідовність байтів LINQ - все з yield
цим унеможливить отримання остаточного розміру, не повторюючи всю послідовність.
Якщо це, звичайно, не реальні вимоги, LINQ може бути ідеально хорошим рішенням (або IList<T>
реалізацією). Однак я припускаю, що Супердумбелл знає, чого хоче.
(EDIT: У мене щойно була думка. Існує велика семантична різниця між створенням копії масивів і лінивим їх читанням. Поміркуйте, що станеться, якщо ви зміните дані в одному з "джерельних" масивів після виклику 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, розгляньте можливість використання оператора виходу 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();