La mejor manera de combinar dos o más matrices de bytes en C #

239

Tengo 3 conjuntos de bytes en C # que necesito combinar en uno. ¿Cuál sería el método más eficiente para completar esta tarea?

Superdumbell
fuente
3
¿Cuáles son específicamente sus requisitos? ¿Está tomando la unión de las matrices o está preservando varias instancias del mismo valor? ¿Desea ordenar los elementos o desea conservar el orden en las matrices iniciales? ¿Busca eficiencia en velocidad o en líneas de código?
jason
Me encanta, "mejor" depende de cuáles sean sus requisitos.
Ady
77
Si puede usar LINQ, puede usar el Concatmétodo:IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne
1
Intenta ser más claro en tus preguntas. Esta vaga pregunta ha causado mucha confusión entre esas personas lo suficientemente buenas como para tomarse el tiempo de responderle.
Drew Noakes el

Respuestas:

327

Para los tipos primitivos (incluidos los bytes), use en System.Buffer.BlockCopylugar de System.Array.Copy. Es mas rapido.

Tomé el tiempo de cada uno de los métodos sugeridos en un bucle ejecutado 1 millón de veces usando 3 matrices de 10 bytes cada una. Aquí están los resultados:

  1. Nueva matriz de bytes usando System.Array.Copy - 0.2187556 segundos
  2. Nueva matriz de bytes usando System.Buffer.BlockCopy - 0.1406286 segundos
  3. IEnumerable <byte> usando el operador de rendimiento C # - 0.0781270 segundos
  4. IEnumerable <byte> usando Concat de LINQ <> - 0.0781270 segundos

Aumenté el tamaño de cada matriz a 100 elementos y volví a ejecutar la prueba:

  1. Nueva matriz de bytes usando System.Array.Copy - 0.2812554 segundos
  2. Nueva matriz de bytes usando System.Buffer.BlockCopy - 0.2500048 segundos
  3. IEnumerable <byte> usando el operador de rendimiento C # - 0.0625012 segundos
  4. IEnumerable <byte> usando Concat de LINQ <> - 0.0781265 segundos

Aumenté el tamaño de cada matriz a 1000 elementos y volví a ejecutar la prueba:

  1. Nueva matriz de bytes usando System.Array.Copy - 1.0781457 segundos
  2. Nueva matriz de bytes usando System.Buffer.BlockCopy - 1.0156445 segundos
  3. IEnumerable <byte> usando el operador de rendimiento C # - 0.0625012 segundos
  4. IEnumerable <byte> usando Concat de LINQ <> - 0.0781265 segundos

Finalmente, aumenté el tamaño de cada matriz a 1 millón de elementos y volví a ejecutar la prueba, ejecutando cada ciclo solo 4000 veces:

  1. Nueva matriz de bytes usando System.Array.Copy - 13.4533833 segundos
  2. Nueva matriz de bytes usando System.Buffer.BlockCopy - 13.1096267 segundos
  3. IEnumerable <byte> usando el operador de rendimiento C # - 0 segundos
  4. IEnumerable <byte> utilizando Concat de LINQ <> - 0 segundos

Entonces, si necesita una nueva matriz de bytes, use

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

Pero, si puede usar un IEnumerable<byte>, DEFINITIVAMENTE prefiera el método Concat <> de LINQ. Es solo un poco más lento que el operador de rendimiento C #, pero es más conciso y más elegante.

IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);

Si tiene un número arbitrario de matrices y está utilizando .NET 3.5, puede hacer que la System.Buffer.BlockCopysolución sea más genérica como esta:

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

* Nota: El bloque anterior requiere que agregue el siguiente espacio de nombres en la parte superior para que funcione.

using System.Linq;

Para el punto de Jon Skeet con respecto a la iteración de las estructuras de datos posteriores (matriz de bytes versus IEnumerable <byte>), volví a ejecutar la última prueba de sincronización (1 millón de elementos, 4000 iteraciones), agregando un ciclo que itera sobre la matriz completa con cada pasar:

  1. Nueva matriz de bytes usando System.Array.Copy - 78.20550510 segundos
  2. Nueva matriz de bytes usando System.Buffer.BlockCopy - 77.89261900 segundos
  3. IEnumerable <byte> usando el operador de rendimiento C # - 551.7150161 segundos
  4. IEnumerable <byte> usando Concat <> de LINQ - 448.1804799 segundos

El punto es que es MUY importante comprender la eficiencia tanto de la creación como del uso de la estructura de datos resultante. Simplemente centrarse en la eficiencia de la creación puede pasar por alto la ineficiencia asociada con el uso. Felicitaciones, Jon.

Matt Davis
fuente
61
Pero, ¿realmente lo está convirtiendo en una matriz al final, como lo requiere la pregunta? Si no, por supuesto, es más rápido, pero no cumple con los requisitos.
Jon Skeet el
18
Re: Matt Davis - No importa si sus "requisitos" necesitan convertir el IEnumerable en una matriz; todo lo que necesitan sus requisitos es que el resultado se use realmente de alguna manera . La razón por la que sus pruebas de rendimiento en IEnumerable son tan bajas es porque en realidad no está haciendo nada . LINQ no realiza ninguno de sus trabajos hasta que intente utilizar los resultados. Por esta razón, encuentro que su respuesta es objetivamente incorrecta y podría llevar a otros a usar LINQ cuando no deberían hacerlo si les importa el rendimiento.
csauve
12
Leí toda la respuesta, incluida su actualización, mi comentario se mantiene. Sé que me uniré tarde a la fiesta, pero la respuesta es muy engañosa y la primera mitad es evidentemente falsa .
csauve
14
¿Por qué la respuesta que contiene información falsa y engañosa es la respuesta más votada, y fue editada para invalidar por completo su declaración original después de que alguien (Jon Skeet) señaló que ni siquiera respondió la pregunta de los OP?
MrCC
3
Respuesta engañosa. Incluso la edición no responde la pregunta.
Serge Profafilecebook
154

Muchas de las respuestas me parecen ignorar los requisitos establecidos:

  • El resultado debería ser una matriz de bytes
  • Debe ser lo más eficiente posible

Estos dos juntos descartan una secuencia LINQ de bytes: cualquier cosa yieldque haga será imposible obtener el tamaño final sin iterar a través de toda la secuencia.

Si esos no son los requisitos reales , por supuesto, LINQ podría ser una solución perfectamente buena (o la IList<T>implementación). Sin embargo, supondré que Superdumbell sabe lo que quiere.

(EDITAR: acabo de tener otro pensamiento. Hay una gran diferencia semántica entre hacer una copia de las matrices y leerlas perezosamente. Considere qué sucede si cambia los datos en una de las matrices "fuente" después de llamar al Combine(o lo que sea ) pero antes de usar el resultado, con una evaluación diferida, ese cambio será visible. Con una copia inmediata, no lo será. Diferentes situaciones requerirán un comportamiento diferente, solo algo a tener en cuenta).

Aquí están mis métodos propuestos, que son muy similares a los contenidos en algunas de las otras respuestas, ciertamente :)

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

Por supuesto, la versión "params" requiere crear primero una matriz de las matrices de bytes, lo que introduce una ineficiencia adicional.

Jon Skeet
fuente
Jon, entiendo exactamente lo que estás diciendo. Mi único punto es que a veces se hacen preguntas con una implementación particular ya en mente sin darse cuenta de que existen otras soluciones. Simplemente dar una respuesta sin ofrecer alternativas me parece un mal servicio. Pensamientos?
Matt Davis, el
1
@Matt: Sí, ofrecer alternativas es bueno, pero vale la pena explicar que son alternativas en lugar de pasarlas por la respuesta a la pregunta que se hace. (No estoy diciendo que hiciste eso, tu respuesta es muy buena)
Jon Skeet
44
(Aunque creo que su marco de actuación debe mostrar el tiempo necesario para pasar por todos los resultados en cada caso, también, para evitar dar la evaluación perezosa una ventaja injusta.)
Jon Skeet
1
Incluso sin cumplir con el requisito de "el resultado debe ser una matriz", simplemente cumplir con el requisito de "el resultado debe ser usado de alguna manera" haría que LINQ no sea óptimo. ¡Creo que el requisito de poder usar el resultado debería ser implícito!
csauve
2
@andleer: Aparte de cualquier otra cosa, Buffer.BlockCopy solo funciona con tipos primitivos.
Jon Skeet
44

Llevé el ejemplo LINQ de Matt un paso más allá para la limpieza del código:

byte[] rv = a1.Concat(a2).Concat(a3).ToArray();

En mi caso, las matrices son pequeñas, por lo que no me preocupa el rendimiento.

Nate Barbettini
fuente
3
Solución corta y simple, una prueba de rendimiento sería genial.
Sebastian
3
Esto es definitivamente claro, legible, no requiere bibliotecas / ayudantes externos y, en términos de tiempo de desarrollo, es bastante eficiente. Excelente cuando el rendimiento en tiempo de ejecución no es crítico.
binki
28

Si simplemente necesita una nueva matriz de bytes, utilice lo siguiente:

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

Alternativamente, si solo necesita un único IEnumerable, considere usar el operador de rendimiento 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;
}
FryGuy
fuente
He hecho algo similar a su segunda opción para fusionar transmisiones grandes, funcionó de maravilla. :)
Greg D
2
La segunda opción es genial. +1.
R. Martinho Fernandes
11

De hecho, me encontré con algunos problemas con el uso de Concat ... (con matrices en los 10 millones, en realidad se bloqueó).

Descubrí que lo siguiente es simple, fácil y funciona lo suficientemente bien sin chocar conmigo, y funciona para CUALQUIER número de matrices (no solo tres) (Utiliza LINQ):

public static byte[] ConcatByteArrays(params byte[][]  arrays)
{
    return arrays.SelectMany(x => x).ToArray();
}
00jt
fuente
6

La clase de flujo de memoria hace este trabajo bastante bien para mí. No pude hacer que la clase de búfer se ejecute tan rápido como el flujo de memoria.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}
Andrés
fuente
3
Como mencionamos, hice una prueba en un ciclo 10,000,000 de veces, y MemoryStream salió 290% MÁS LENTO que Buffer.BlockCopy
esac
En algunos casos, puede estar iterando sobre una serie de matrices sin conocimiento previo de las longitudes de las matrices individuales. Esto funciona bien en este escenario. BlockCopy se basa en tener una matriz de destino preparada previamente
Sentinel
Como dijo @Sentinel, esta respuesta es perfecta para mí porque no conozco el tamaño de las cosas que tengo que escribir y me permite hacer las cosas de manera muy limpia. ¡También funciona bien con .NET Core 3 [ReadOnly] Span <byte>!
Agua
Si inicializa MemoryStream con el tamaño final del tamaño, no se volverá a crear y será más rápido @esac.
Tono Nam
2
    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;
    }
edd
fuente
Lamentablemente, esto no funcionará con todos los tipos. Marshal.SizeOf () no podrá devolver un tamaño para muchos tipos (intente usar este método con matrices de cadenas y verá una excepción "Tipo 'System.String' no se puede ordenar como una estructura no administrada; no hay un tamaño significativo o se puede calcular el desplazamiento ". Podría intentar limitar el parámetro de tipo solo a tipos de referencia (agregando where T : struct), pero, al no ser un experto en las entrañas del CLR, no podría decir si podría obtener excepciones en ciertas estructuras también (por ejemplo, si contienen campos de tipo de referencia).
Daniel Scott,
2
    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();
        }
    }
Peter Ertl
fuente
Su respuesta podría ser mejor si hubiera publicado una pequeña explicación de lo que muestra este código.
AFract
1
sí concatena un conjunto de conjuntos de bytes en un conjunto de bytes grande (como este): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl
1

Puede usar genéricos para combinar matrices. El siguiente código se puede ampliar fácilmente a tres matrices. De esta manera, nunca necesita duplicar el código para diferentes tipos de matrices. Algunas de las respuestas anteriores me parecen demasiado complejas.

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;
    }
BajaPaul
fuente
0

Aquí hay una generalización de la respuesta proporcionada por @Jon Skeet. Básicamente es lo mismo, solo que es utilizable para cualquier tipo de matriz, no solo bytes:

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;
}
jefe
fuente
3
¡PELIGRO! Estos métodos no funcionarán con ningún tipo de matriz con elementos de más de un byte (prácticamente todo lo que no sean matrices de bytes). Buffer.BlockCopy () funciona con cantidades de bytes, no con números de elementos de matriz. La razón por la que se puede usar fácilmente con una matriz de bytes es que cada elemento de la matriz es un solo byte, por lo que la longitud física de la matriz es igual al número de elementos. Para convertir los métodos de byte [] de John en métodos genéricos, deberá multiplicar todos los desplazamientos y longitudes por la longitud de bytes de un único elemento de matriz; de lo contrario, no copiará todos los datos.
Daniel Scott
2
Normalmente, para hacer que esto funcione, calcularía el tamaño de un solo elemento utilizando sizeof(...)y multiplicaría eso por el número de elementos que desea copiar, pero sizeof no se puede utilizar con un tipo genérico. Es posible, para algunos tipos, usar Marshal.SizeOf(typeof(T)), pero obtendrá errores de tiempo de ejecución con ciertos tipos (por ejemplo, cadenas). Alguien con un conocimiento más profundo del funcionamiento interno de los tipos CLR podrá señalar todas las trampas posibles aquí. Baste decir que escribir un método genérico de concatenación de matriz [usando BlockCopy] no es trivial.
Daniel Scott
2
Y finalmente, puede escribir un método genérico de concatenación de matrices como este casi exactamente de la manera que se muestra arriba (con un rendimiento ligeramente inferior) utilizando Array.Copy en su lugar. Simplemente reemplace todas las llamadas Buffer.BlockCopy con llamadas Array.Copy.
Daniel Scott, el
0
    /// <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();
Mehmet ÜNLÜ
fuente
Gracias por la contribucion. Como ya hay una serie de respuestas altamente calificadas a esto de hace más de una década, sería útil ofrecer una explicación de lo que distingue su enfoque. ¿Por qué alguien debería usar esto en lugar de, por ejemplo, la respuesta aceptada?
Jeremy Caney
Me gusta usar métodos extendidos, porque hay un código claro para entender. Este código selecciona dos matrices con índice de inicio y cuenta y concat. También este método extendido. Entonces, esto es para todos los tipos de matriz listos para todos los tiempos
Mehmet ÜNLÜ
¡Eso tiene sentido para mí! ¿Te importa editar tu pregunta para incluir esa información? Creo que sería valioso para los futuros lectores tener eso por adelantado, para que puedan distinguir rápidamente su enfoque de las respuestas existentes. ¡Gracias!
Jeremy Caney
-1

Todo lo que necesita para pasar la lista de matrices de bytes y esta función le devolverá la matriz de bytes (fusionada). Esta es la mejor solución, creo :).

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

Concat es la respuesta correcta, pero por alguna razón, una cosa manipulada es la que obtiene más votos. Si le gusta esa respuesta, tal vez le gustaría esta solución más general aún más:

    IEnumerable<byte> Combine(params byte[][] arrays)
    {
        foreach (byte[] a in arrays)
            foreach (byte b in a)
                yield return b;
    }

que te permitiría hacer cosas como:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();
Mark Maxham
fuente
55
La pregunta específicamente pide la solución más eficiente . Enumerable.ToArray no va a ser muy eficiente, ya que no puede saber el tamaño de la matriz final para comenzar, mientras que las técnicas enrolladas a mano sí pueden.
Jon Skeet el