Лучший способ объединить два или более байтовых массивов в C#



У меня есть 3 байтовых массива в C#, которые мне нужно объединить в один. Каким будет наиболее эффективный метод для выполнения этой задачи?

652   12  

12 ответов:

для примитивных типов (включая байт), используйте System.Buffer.BlockCopy вместо System.Array.Copy. Это быстрее.

я рассчитал каждый из предложенных методов в цикле, выполненном 1 миллион раз, используя 3 массива по 10 байт каждый. Вот результаты:

  1. новый массив байтов с помощью System.Array.Copy - 0.2187556 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 0.1406286 секунд
  3. IEnumerable с помощью оператора yield C# - 0.0781270 секунд
  4. IEnumerable с помощью Linq'S Concat - 0.0781270 секунд

я увеличил размер каждого массива до 100 элементов и повторно выполнил тест:

  1. новый массив байтов с помощью System.Array.Copy - 0.2812554 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 0.2500048 секунд
  3. IEnumerable используя оператор выхода C# - 0.0625012 секунды
  4. IEnumerable с помощью Linq'S Concat - 0.0781265 секунд

я увеличил размер массива до 1000 элементов и повторно провела тест:

  1. новый массив байтов с помощью System.Array.Copy - 1.0781457 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 1.0156445 секунд
  3. IEnumerable используя оператор выхода C# - 0.0625012 секунды
  4. IEnumerable с помощью Linq'S Concat - 0.0781265 секунд

наконец, я увеличил размер каждого массива до 1 млн элементов и повторный запуск теста, выполнение каждого цикла только 4000 раз:

  1. новый массив байтов с помощью System.Array.Copy - 13.4533833 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 13.1096267 секунд
  3. IEnumerable используя оператор выхода C# - 0 секунд
  4. IEnumerable с помощью Linq'S Concat - 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>,наверняка предпочитаю метод Linq Concat. Он только немного медленнее, чем оператор yield 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;

к точке Джона Скита относительно итерации последующих структур данных (byte array vs. IEnumerable), я повторно выполнил последний тест синхронизации (1 миллион элементов, 4000 итераций), добавив цикл, который повторяется по всему массиву с каждым проходом:

  1. новый массив байтов с помощью System.Array.Copy - 78.20550510 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 77.89261900 секунд
  3. IEnumerable с помощью C# выход оператора-551.7150161 сек
  4. IEnumerable с помощью Linq'S Concat - 448.1804799 секунд

дело в том, что это очень важно понимать эффективность обоих творений и использование результирующей структуры данных. Просто сосредоточив внимание на эффективности создания может упустить из виду неэффективность, связанную с использованием. Спасибо, Джон.

многие из ответов, как мне кажется, игнорируют заявленные требования:

  • результатом должен быть массив байтов
  • он должен быть максимально эффективным

эти два вместе исключают последовательность 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, рассмотрите возможность использования оператора 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();
}

класс memorystream делает эту работу довольно хорошо для меня. Я не мог заставить класс буфера работать так же быстро, как 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;
    }
    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();
        }
    }

вот обобщение ответа, предоставленного @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;
}

можно использовать дженерики для объединения массивов. Следующий код может быть легко расширен до трех массивов. Таким образом, вам никогда не нужно дублировать код для различных типов массивов. Некоторые из приведенных выше ответов кажутся мне слишком сложными.

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;
    }

все, что вам нужно передать список байтовых массивов и эта функция вернет вам массив байтов (слили). Это лучшее решение, которое я думаю :).

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();

Comments

    Ничего не найдено.