Melhor maneira de combinar duas ou mais matrizes de bytes em C #


238

Eu tenho matrizes de 3 bytes em C # que preciso combinar em uma. Qual seria o método mais eficiente para concluir esta tarefa?


3
Quais são especificamente os seus requisitos? Você está adotando a união das matrizes ou está preservando várias instâncias do mesmo valor? Deseja classificar os itens ou preservar a ordem nas matrizes iniciais? Você está procurando eficiência em velocidade ou em linhas de código?
jason

Adore, "o melhor" depende de quais são suas necessidades.
213 Ady

7
Se você é capaz de usar LINQ, então você pode simplesmente usar o Concatmétodo:IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne

1
Por favor, tente ser mais claro em suas perguntas. Essa vaga pergunta causou muita confusão entre as pessoas boas o suficiente para reservar um tempo para responder a você.
Drew Noakes

Respostas:


326

Para tipos primitivos (incluindo bytes), use em System.Buffer.BlockCopyvez de System.Array.Copy. É mais rápido.

Cronometrei cada um dos métodos sugeridos em um loop executado 1 milhão de vezes usando 3 matrizes de 10 bytes cada. Aqui estão os resultados:

  1. Nova matriz de bytes usando System.Array.Copy - 0.2187556 segundos
  2. Nova matriz de bytes usando System.Buffer.BlockCopy - 0,1406286 segundos
  3. IEnumerable <byte> usando o operador de rendimento C # - 0.0781270 segundos
  4. IEnumerable <byte> usando Concat do LINQ <> - 0.0781270 segundos

Aumentei o tamanho de cada matriz para 100 elementos e refiz o teste:

  1. Nova matriz de bytes usando System.Array.Copy - 0,2812554 segundos
  2. Nova matriz de bytes usando System.Buffer.BlockCopy - 0,2500048 segundos
  3. IEnumerable <byte> usando o operador de rendimento C # - 0.0625012 segundos
  4. IEnumerable <byte> usando Concat do LINQ <> - 0.0781265 segundos

Aumentei o tamanho de cada matriz para 1000 elementos e refiz o teste:

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

Por fim, aumentei o tamanho de cada matriz para 1 milhão de elementos e refiz o teste, executando cada loop apenas 4000 vezes:

  1. Nova matriz de bytes usando System.Array.Copy - 13.4533833 segundos
  2. Nova matriz de bytes usando System.Buffer.BlockCopy - 13.1096267 segundos
  3. IEnumerable <byte> usando o operador de rendimento C # - 0 segundos
  4. IEnumerable <byte> usando Concat do LINQ <> - 0 segundos

Portanto, se você precisar de uma nova 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);

Mas, se você puder usar um IEnumerable<byte>, DEFINITIVAMENTE prefira o método Concat <> do LINQ. É apenas um pouco mais lento que o operador de rendimento C #, mas é mais conciso e mais elegante.

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

Se você tiver um número arbitrário de matrizes e estiver usando o .NET 3.5, poderá tornar a System.Buffer.BlockCopysolução mais 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: O bloco acima requer a adição do seguinte espaço para nome na parte superior para que ele funcione.

using System.Linq;

Para o argumento de Jon Skeet em relação à iteração das estruturas de dados subsequentes (matriz de bytes vs. IEnumerable <byte>), executei novamente o último teste de temporização (1 milhão de elementos, 4000 iterações), adicionando um loop que itera sobre a matriz completa a cada passar:

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

O ponto é que é MUITO importante entender a eficiência da criação e do uso da estrutura de dados resultante. O simples foco na eficiência da criação pode ignorar a ineficiência associada ao uso. Parabéns, Jon.


61
Mas você está realmente convertendo-o em uma matriz no final, conforme a pergunta exige? Caso contrário, é claro que é mais rápido - mas não está cumprindo os requisitos.
Jon Skeet

18
Re: Matt Davis - Não importa se seus "requisitos" precisam transformar o IEnumerable em uma matriz - tudo o que seus requisitos precisam é que o resultado seja realmente usado em alguma fase . A razão pela qual seus testes de desempenho no IEnumerable são tão baixos é porque você não está realmente fazendo nada ! O LINQ não executa nenhum de seus trabalhos até você tentar usar os resultados. Por esse motivo, acho sua resposta objetivamente incorreta e poderia levar outras pessoas a usar o LINQ quando não deveriam, se não se importassem com o desempenho.
Csauve

12
Eu li toda a resposta, incluindo sua atualização, meu comentário permanece. Sei que estou entrando na festa tarde, mas a resposta é muito enganadora e a primeira metade é claramente falsa .
Csauve

14
Por que a resposta que contém informações falsas e enganosas é a resposta mais votada e foi editada para invalidar completamente sua declaração original depois que alguém (Jon Skeet) apontou que nem sequer respondeu à pergunta do OP?
MrCC

3
Resposta enganosa. Até a edição não está respondendo à pergunta.
Serge Profafilecebook

154

Muitas das respostas me parecem ignorar os requisitos declarados:

  • O resultado deve ser uma matriz de bytes
  • Deve ser o mais eficiente possível

Esses dois juntos descartam uma sequência de bytes LINQ - qualquer coisa com yieldisso tornará impossível obter o tamanho final sem iterar por toda a sequência.

Se esses não são os requisitos reais , é claro, o LINQ poderia ser uma solução perfeitamente boa (ou a IList<T>implementação). No entanto, vou assumir que Superdumbell sabe o que quer.

(EDIT: Acabei de ter outro pensamento. Há uma grande diferença semântica entre fazer uma cópia das matrizes e lê-las preguiçosamente. Considere o que acontece se você alterar os dados em uma das matrizes "de origem" depois de chamar o Combine(ou o que seja ), mas antes de usar o resultado - com uma avaliação lenta, essa alteração será visível. Com uma cópia imediata, não. Situações diferentes exigirão um comportamento diferente - apenas algo para estar ciente.)

Aqui estão meus métodos propostos - que são muito semelhantes aos contidos em algumas das outras respostas, certamente :)

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

Obviamente, a versão "params" requer a criação de uma matriz de matrizes de bytes primeiro, o que introduz ineficiência extra.


Jon, eu entendo exatamente o que você está dizendo. Meu único argumento é que algumas vezes as perguntas são feitas com uma implementação específica já em mente, sem perceber que existem outras soluções. Simplesmente fornecer uma resposta sem oferecer alternativas me parece um desserviço. Pensamentos?
Matt Davis

1
@ Matt: Sim, oferecer alternativas é bom - mas vale a pena explicar que elas são alternativas, em vez de passar adiante como resposta à pergunta que está sendo feita. (Eu não estou dizendo que você fez isso -. A sua resposta é muito bom)
Jon Skeet

4
(Embora eu acho que sua avaliação de desempenho deve mostrar o tempo necessário para percorrer todos os resultados em cada caso, também, para evitar dar avaliação preguiçosa uma vantagem injusta.)
Jon Skeet

1
Mesmo sem atender ao requisito de "resultado deve ser uma matriz", simplesmente atender a um requisito de "resultado deve ser usado em algum momento" tornaria o LINQ não ideal. Eu acho que esse requisito para poder usar o resultado deve estar implícito!
Csauve

2
@andleer: Além de qualquer outra coisa, o Buffer.BlockCopy funciona apenas com tipos primitivos.
Jon Skeet

44

Levei o exemplo de LINQ de Matt um passo adiante para a limpeza do código:

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

No meu caso, as matrizes são pequenas, então não estou preocupado com o desempenho.


3
Solução curta e simples, um teste de desempenho seria ótimo!
Sebastian

3
Isso é definitivamente claro, legível, não requer bibliotecas / auxiliares externos e, em termos de tempo de desenvolvimento, é bastante eficiente. Ótimo quando o desempenho em tempo de execução não é crítico.
precisa saber é

28

Se você simplesmente precisar de uma nova matriz de bytes, use o seguinte:

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

Como alternativa, se você precisar apenas de um único IEnumerable, considere usar o operador de rendimento 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;
}

Fiz algo semelhante à sua segunda opção para mesclar fluxos grandes, funcionou como um encanto. :)
Greg D

2
A segunda opção é ótima. +1.
R. Martinho Fernandes

10

Na verdade, eu tive alguns problemas com o uso do Concat ... (com matrizes nos 10 milhões, ele realmente travou).

Achei o seguinte simples, fácil e funciona bem o suficiente sem ter de travar comigo, e funciona para QUALQUER número de matrizes (não apenas três) (ele usa LINQ):

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

6

A classe memorystream faz esse trabalho muito bem para mim. Não consegui que a classe de buffer funcionasse tão rápido quanto o fluxo de memória.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}

3
Como qwe afirmou, fiz um teste em loop 10.000.000 de vezes, e o MemoryStream saiu 290% MAIS LENTO que o Buffer
esac

Em alguns casos, você pode estar iterando sobre um número incontável de matrizes sem nenhum conhecimento prévio dos comprimentos individuais da matriz. Isso funciona bem nesse cenário. BlockCopy depende de ter uma matriz de destino precreated
Sentinela

Como o @Sentinel disse, esta resposta é perfeita para mim porque não tenho conhecimento do tamanho das coisas que tenho que escrever e me permite fazer as coisas com muita clareza. Também funciona bem com o [ReadOnly] Span <byte> do .NET Core 3!
Água

Se você inicializar o MemoryStream com o tamanho final do tamanho, ele não será recriado e será mais 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;
    }

Infelizmente, isso não funcionará com todos os tipos. Marshal.SizeOf () não poderá retornar um tamanho para muitos tipos (tente usar esse método com matrizes de seqüências de caracteres e você verá uma exceção "O tipo 'System.String' não pode ser empacotado como uma estrutura não gerenciada; nenhum tamanho significativo ou offset pode ser calculado ". Você poderia tentar limitar o parâmetro type apenas aos tipos de referência (adicionando where T : struct), mas - não sendo um especialista nas entranhas do CLR - não sabia dizer se também poderia haver exceções em certas estruturas (por exemplo, se eles contêm campos de tipo de referência).
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();
        }
    }

Sua resposta poderia ser melhor se você tivesse postado uma pequena explicação sobre o que esse código de exemplo.
AFract

1
concatena uma matriz de matrizes de bytes em uma matriz de bytes grande (como esta): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl

1

Pode usar genéricos para combinar matrizes. O código a seguir pode ser facilmente expandido para três matrizes. Dessa forma, você nunca precisará duplicar o código para diferentes tipos de matrizes. Algumas das respostas acima parecem muito complexas para mim.

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

0

Aqui está uma generalização da resposta fornecida por @Jon Skeet. É basicamente o mesmo, mas é utilizável para qualquer tipo de matriz, não apenas 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;
}

3
PERIGO! Esses métodos não funcionarão com nenhum tipo de matriz com elementos maiores que um byte (praticamente tudo que não sejam matrizes de bytes). Buffer.BlockCopy () trabalha com quantidades de bytes, não com números de elementos da matriz. O motivo pelo qual ele pode ser usado facilmente com uma matriz de bytes é que cada elemento da matriz é um único byte; portanto, o comprimento físico da matriz é igual ao número de elementos. Para transformar os métodos de byte [] de John em métodos genéricos, você precisará multiplicar todos os deslocamentos e comprimentos pelo comprimento de bytes de um único elemento da matriz - caso contrário, você não copiará todos os dados.
Daniel Scott

2
Normalmente, para fazer isso funcionar, você calcula o tamanho de um único elemento usando sizeof(...)e multiplica pelo número de elementos que deseja copiar, mas sizeof não pode ser usado com um tipo genérico. É possível - para alguns tipos - usar Marshal.SizeOf(typeof(T)), mas você obterá erros de tempo de execução com certos tipos (por exemplo, strings). Alguém com um conhecimento mais profundo do funcionamento interno dos tipos de CLR poderá apontar todas as possíveis armadilhas aqui. Basta dizer que escrever um método genérico de concatenação de matriz [usando BlockCopy] não é trivial.
Daniel Scott

2
E finalmente - você pode escrever um método genérico de concatenação de matrizes como esse quase da maneira mostrada acima (com desempenho ligeiramente inferior) usando Array.Copy. Apenas substitua todas as chamadas Buffer.BlockCopy por chamadas Array.Copy.
Daniel Scott

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

Obrigado pela contribuição. Como já existem várias respostas altamente classificadas para isso de mais de uma década atrás, seria útil oferecer uma explicação sobre o que distingue sua abordagem. Por que alguém deveria usar isso em vez de, por exemplo, a resposta aceita?
Jeremy Caney

Eu gosto de usar métodos estendidos, porque há um código claro para entender. Este código seleciona duas matrizes com índice inicial, contagem e concat. Este método também foi estendido. Portanto, isso é para todos os tipos de matriz prontos para todos os tempos
Mehmet ÜNLÜ

Isso faz sentido para mim! Você se importa de editar sua pergunta para incluir essas informações? Eu acho que seria valioso para os futuros leitores ter isso com antecedência, para que eles possam distinguir rapidamente sua abordagem das respostas existentes. Obrigado!
Jeremy Caney

-1

Tudo o que você precisa para passar na lista de matrizes de bytes e essa função retornará a matriz de bytes (mesclada). Esta é a melhor solução que eu acho :).

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

-5

Concat é a resposta certa, mas, por alguma razão, uma coisa de mão está recebendo mais votos. Se você gosta dessa resposta, talvez queira essa solução mais geral ainda mais:

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

o que permitiria fazer coisas como:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();

5
A pergunta pede especificamente a solução mais eficiente . Enumerable.ToArray não será muito eficiente, pois não pode saber o tamanho da matriz final para começar - ao passo que as técnicas feitas manualmente.
Jon Skeet
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.