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

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

12 ответов


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

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

  1. новый массив байтов с помощью System.Array.Copy - 0.2187556 секунд
  2. новый массив байтов с помощью System.Buffer.BlockCopy - 0.1406286 секунд
  3. IEnumerable используя оператор выхода C# - 0.0781270 секунд
  4. IEnumerable используя Конкат LINQ - 0.0781270 секунд

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

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

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

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

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

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

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

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


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

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

эти два вместе исключают последовательность LINQ байтов-ничего с yield сделает невозможным получить окончательный размер без итерации через всю последовательность.

если это не реальные требования конечно, LINQ может быть совершенно хорошее решение (или IList<T> реализации). Тем не менее, я предполагаю, что Superdumbell знает, чего он хочет.

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

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