Il modo migliore per combinare due o più array di byte in C #


239

Ho array di 3 byte in C # che devo combinare in uno. Quale sarebbe il metodo più efficiente per completare questa attività?


3
Quali sono specificamente le tue esigenze? Stai prendendo l'unione delle matrici o stai conservando più istanze dello stesso valore? Vuoi ordinare gli articoli o vuoi conservare l'ordinamento negli array iniziali? Cerchi efficienza nella velocità o nelle righe di codice?
Jason,

Lo adoro, "il migliore" dipende da quali sono le tue esigenze.
Ady,

7
Se sei in grado di utilizzare LINQ, puoi semplicemente utilizzare il Concatmetodo:IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne

1
Cerca di essere più chiaro nelle tue domande. Questa vaga domanda ha suscitato molta confusione tra quelle persone abbastanza buone da prendersi il tempo di risponderti.
Drew Noakes,

Risposte:


327

Per i tipi primitivi (inclusi i byte), utilizzare System.Buffer.BlockCopyinvece diSystem.Array.Copy . È più veloce.

Ho cronometrato ciascuno dei metodi suggeriti in un ciclo eseguito 1 milione di volte usando 3 array di 10 byte ciascuno. Ecco i risultati:

  1. Nuovo array di byte con System.Array.Copy - 0,2187556 secondi
  2. Nuovo array di byte con System.Buffer.BlockCopy - 0,1406286 secondi
  3. IEnumerable <byte> utilizzando l'operatore di rendimento C # - 0,0781270 secondi
  4. IEnumerable <byte> utilizzando il Concat <> di LINQ - 0,0781270 secondi

Ho aumentato la dimensione di ciascun array a 100 elementi e rieseguito il test:

  1. Nuovo array di byte con System.Array.Copy - 0,2812554 secondi
  2. Nuovo array di byte con System.Buffer.BlockCopy - 0,2500048 secondi
  3. IEnumerable <byte> utilizzando l'operatore di rendimento C # - 0,0625012 secondi
  4. IEnumerable <byte> utilizzando il Concat <> di LINQ - 0,0781265 secondi

Ho aumentato la dimensione di ciascun array a 1000 elementi e ho eseguito nuovamente il test:

  1. Nuova matrice di byte usando System.Array.Copy - 1.0781457 secondi
  2. Nuova matrice di byte utilizzando System.Buffer.BlockCopy - 1,0156445 secondi
  3. IEnumerable <byte> utilizzando l'operatore di rendimento C # - 0,0625012 secondi
  4. IEnumerable <byte> utilizzando il Concat <> di LINQ - 0,0781265 secondi

Infine, ho aumentato le dimensioni di ciascun array a 1 milione di elementi e ho eseguito nuovamente il test, eseguendo ogni ciclo solo 4000 volte:

  1. Nuovo array di byte con System.Array.Copy - 13,4533833 secondi
  2. Nuovo array di byte con System.Buffer.BlockCopy - 13.1096267 secondi
  3. IEnumerable <byte> utilizzando l'operatore di rendimento C # - 0 secondi
  4. IEnumerable <byte> utilizzando il Concat <> di LINQ - 0 secondi

Quindi, se hai bisogno di un nuovo array di byte, usa

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

Ma, se puoi usare un IEnumerable<byte>, Sicuramente preferisci il metodo Concat <> di LINQ. È solo leggermente più lento dell'operatore di rendimento C #, ma è più conciso ed elegante.

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

Se si dispone di un numero arbitrario di matrici e si utilizza .NET 3.5, è possibile rendere la System.Buffer.BlockCopysoluzione più generica in questo modo:

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: il blocco sopra riportato richiede l'aggiunta del seguente spazio dei nomi in alto affinché funzioni.

using System.Linq;

Per quanto riguarda Jon Skeet riguardo all'iterazione delle successive strutture di dati (array di byte vs. IEnumerable <byte>), ho rieseguito l'ultimo test di temporizzazione (1 milione di elementi, 4000 iterazioni), aggiungendo un ciclo che scorre sull'intera matrice con ciascuno passaggio:

  1. Nuovo array di byte con System.Array.Copy - 78.20550510 secondi
  2. Nuovo array di byte con System.Buffer.BlockCopy - 77,89261900 secondi
  3. IEnumerable <byte> utilizzando l'operatore di rendimento C # - 551.7150161 secondi
  4. IEnumerable <byte> utilizzando il Concat <> di LINQ - 448.1804799 secondi

Il punto è che è MOLTO importante comprendere l'efficienza sia della creazione che dell'utilizzo della struttura dei dati risultante. Concentrarsi semplicemente sull'efficienza della creazione può trascurare l'inefficienza associata all'utilizzo. Kudos, Jon.


61
Ma alla fine lo stai convertendo in un array, come richiede la domanda? In caso contrario, ovviamente è più veloce, ma non soddisfa i requisiti.
Jon Skeet,

18
Ri: Matt Davis - Non importa se i tuoi "requisiti" devono trasformare IEnumerable in un array - tutto ciò di cui hai bisogno è che il risultato sia effettivamente utilizzato in qualche modo . Il motivo per cui i tuoi test delle prestazioni su IEnumerable sono così bassi è perché in realtà non stai facendo nulla ! LINQ non esegue alcuna delle sue attività finché non si tenta di utilizzare i risultati. Per questo motivo trovo la tua risposta obiettivamente errata e potrei indurre gli altri a utilizzare LINQ quando assolutamente non dovrebbero preoccuparsene delle prestazioni.
csauve,

12
Ho letto l'intera risposta incluso il tuo aggiornamento, il mio commento è valido. So che mi unirò alla festa tardi, ma la risposta è gravemente fuorviante e la prima metà è palesemente falsa .
csauve,

14
Perché la risposta che contiene informazioni false e fuorvianti è la risposta più votata e sostanzialmente modificata invalidare sua dichiarazione originale dopo che qualcuno (Jon Skeet) ha sottolineato che non ha nemmeno risposto alla domanda dei PO?
MrCC,

3
Risposta fuorviante. Anche l'edizione non risponde alla domanda.
Serge Profafilecebook,

154

Molte delle risposte mi sembrano ignorare i requisiti dichiarati:

  • Il risultato dovrebbe essere un array di byte
  • Dovrebbe essere il più efficiente possibile

Questi due insieme escludono una sequenza di byte LINQ - qualsiasi cosa yieldrenderà impossibile ottenere la dimensione finale senza scorrere l'intera sequenza.

Se quelli non sono i reali requisiti, LINQ potrebbe essere una soluzione perfettamente valida (o l' IList<T>implementazione). Tuttavia, suppongo che Superdumbell sappia cosa vuole.

(EDIT: Ho appena avuto un altro pensiero. C'è una grande differenza semantica tra fare una copia degli array e leggerli pigramente. Considera cosa succede se cambi i dati in uno degli array "source" dopo aver chiamato il Combine (o qualunque cosa ) ma prima di usare il risultato - con una valutazione pigra, quel cambiamento sarà visibile. Con una copia immediata, non lo sarà. Diverse situazioni richiedono comportamenti diversi - solo qualcosa di cui essere consapevoli.)

Ecco i miei metodi proposti - che sono molto simili a quelli contenuti in alcune delle altre risposte, 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;
}

Ovviamente la versione "params" richiede prima di creare un array di array di byte, il che introduce ulteriore inefficienza.


Jon, capisco esattamente cosa stai dicendo. Il mio unico punto è che a volte vengono poste domande tenendo presente una particolare implementazione senza rendersi conto che esistono altre soluzioni. Fornire semplicemente una risposta senza offrire alternative mi sembra un disservizio. Pensieri?
Matt Davis,

1
@Matt: Sì, l'offerta di alternative è buona, ma vale la pena spiegare che sono alternative piuttosto che passarle come risposta alla domanda che viene posta. (Non sto dicendo che l'hai fatto - la tua risposta è molto buona.)
Jon Skeet il

4
(Anche se penso che il tuo benchmark delle prestazioni dovrebbe mostrare il tempo impiegato per esaminare tutti i risultati in ogni caso, anche per evitare di dare un vantaggio sleale alla valutazione pigra.)
Jon Skeet,

1
Anche senza soddisfare il requisito di "risultato deve essere un array", semplicemente soddisfare un requisito di "risultato deve essere utilizzato in qualche modo" renderebbe LINQ non ottimale. Penso che il requisito per poter utilizzare il risultato dovrebbe essere implicito!
csauve,

2
@andleer: A parte tutto il resto, Buffer.BlockCopy funziona solo con tipi primitivi.
Jon Skeet,

44

Ho preso l'esempio LINQ di Matt un ulteriore passo avanti per la pulizia del codice:

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

Nel mio caso, gli array sono piccoli, quindi non sono preoccupato per le prestazioni.


3
Soluzione breve e semplice, un test delle prestazioni sarebbe fantastico!
Sebastian

3
Questo è sicuramente chiaro, leggibile, non richiede librerie / helpers esterni e, in termini di tempo di sviluppo, è abbastanza efficiente. Ottimo quando le prestazioni di runtime non sono fondamentali.
binki,

28

Se hai semplicemente bisogno di un nuovo array di byte, utilizza quanto segue:

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

In alternativa, se hai solo bisogno di un singolo IEnumerable, considera l'utilizzo dell'operatore di 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;
}

Ho fatto qualcosa di simile alla tua seconda opzione per unire grandi flussi, ha funzionato come un fascino. :)
Greg D,

2
La seconda opzione è fantastica. +1.
R. Martinho Fernandes,

11

In realtà ho riscontrato alcuni problemi con l'utilizzo di Concat ... (con array da 10 milioni, in realtà si è bloccato).

Ho trovato quanto segue semplice, facile e funziona abbastanza bene senza andare in crash su di me, e funziona per QUALSIASI numero di array (non solo tre) (utilizza LINQ):

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

6

La classe memorystream fa questo lavoro abbastanza bene per me. Non sono riuscito a far funzionare la classe buffer alla velocità della memoria.

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

3
Come affermato da qwe, ho fatto un test in un ciclo 10.000.000 di volte, e MemoryStream è risultato 290% PIÙ LENTO del Buffer.BlockCopy
esac,

In alcuni casi è possibile che si stia iterando su un numero infinito di array senza alcuna conoscenza preliminare delle singole lunghezze dell'array. Funziona bene in questo scenario. BlockCopy fa affidamento sul pretrattamento di un array di destinazione
Sentinel,

Come ha detto @Sentinel, questa risposta è perfetta per me perché non ho conoscenza delle dimensioni delle cose che devo scrivere e mi permette di fare le cose in modo molto pulito. Funziona bene anche con [ReadOnly] Span <byte> di .NET Core 3!
Acqua

Se si inizializza MemoryStream con la dimensione finale della dimensione, questa non verrà ricreata e sarà @esac più veloce.
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;
    }

Purtroppo questo non funziona con tutti i tipi. Marshal.SizeOf () non sarà in grado di restituire una dimensione per molti tipi (prova a utilizzare questo metodo con matrici di stringhe e vedrai un'eccezione "Tipo 'System.String' non può essere marshallato come una struttura non gestita; nessuna dimensione significativa o offset può essere calcolato ". Potresti provare a limitare il parametro type solo ai tipi di riferimento (aggiungendo where T : struct), ma - non essendo un esperto nelle viscere del CLR - non potrei dire se potresti ottenere eccezioni anche su determinate strutture (ad es. se contengono campi del tipo di riferimento)
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();
        }
    }

La tua risposta potrebbe essere migliore se avessi pubblicato una piccola spiegazione di cosa fa questo esempio di codice.
Dopo il

1
concatena una matrice di array di byte in una matrice di byte di grandi dimensioni (in questo modo): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl,

1

Può usare generici per combinare array. Il codice seguente può essere facilmente esteso a tre matrici. In questo modo non è mai necessario duplicare il codice per diversi tipi di array. Alcune delle risposte di cui sopra mi sembrano eccessivamente complesse.

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

Ecco una generalizzazione della risposta fornita da @Jon Skeet. È sostanzialmente lo stesso, solo è utilizzabile per qualsiasi tipo di array, non solo byte:

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
PERICOLO! Questi metodi non funzionano con qualsiasi tipo di array con elementi più lunghi di un byte (praticamente tutto tranne gli array di byte). Buffer.BlockCopy () funziona con quantità di byte, non numeri di elementi dell'array. Il motivo per cui può essere utilizzato facilmente con un array di byte è che ogni elemento dell'array è un singolo byte, quindi la lunghezza fisica dell'array è uguale al numero di elementi. Per trasformare i metodi byte [] di John in metodi generici dovrai moltiplicare tutti gli offset e le lunghezze per la lunghezza in byte di un singolo elemento dell'array, altrimenti non copierai tutti i dati.
Daniel Scott,

2
Normalmente per fare questo lavoro calcoleresti la dimensione di un singolo elemento usando sizeof(...)e moltiplicandolo per il numero di elementi che vuoi copiare, ma sizeof non può essere usato con un tipo generico. È possibile - per alcuni tipi - da usare Marshal.SizeOf(typeof(T)), ma si otterranno errori di runtime con determinati tipi (ad es. Stringhe). Qualcuno con una conoscenza più approfondita del funzionamento interno dei tipi di CLR sarà in grado di indicare qui tutte le possibili trappole. Basti dire che scrivere un metodo di concatenazione di array generico [usando BlockCopy] non è banale.
Daniel Scott,

2
E infine - puoi scrivere un metodo di concatenazione di array generico come questo quasi esattamente nel modo mostrato sopra (con prestazioni leggermente inferiori) usando invece Array.Copy. Sostituisci semplicemente tutte le chiamate Buffer.BlockCopy con le chiamate 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();

Grazie per il contributo Dal momento che esistono già numerose risposte molto apprezzate da oltre un decennio fa, sarebbe utile offrire una spiegazione di ciò che distingue il tuo approccio. Perché qualcuno dovrebbe usare questo invece di es. La risposta accettata?
Jeremy Caney,

Mi piace usare metodi estesi, perché ci sono codici chiari per capire. Questo codice seleziona due matrici con indice iniziale e conteggio e concat. Anche questo metodo è stato esteso. Quindi, questo è per tutti i tipi di array pronti per tutti i tempi
Mehmet ÜNLÜ

Questo ha senso per me! Ti dispiace modificare la tua domanda per includere tali informazioni? Penso che sarebbe utile per i futuri lettori avere questo in anticipo, in modo che possano rapidamente distinguere il tuo approccio dalle risposte esistenti. Grazie!
Jeremy Caney,

-1

Tutto ciò che serve per passare un elenco di array di byte e questa funzione restituirà l'array di byte (unito). Questa è la migliore soluzione che penso :).

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 è la risposta giusta, ma per qualche motivo una cosa handrolling sta ottenendo il maggior numero di voti. Se ti piace quella risposta, forse ti piacerebbe ancora di più questa soluzione più generale:

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

che ti permetterebbe di fare cose come:

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

5
La domanda richiede specificamente la soluzione più efficiente . Enumerable.ToArray non sarà molto efficiente, in quanto non è in grado di conoscere le dimensioni dell'array finale con cui iniziare, mentre le tecniche fatte a mano possono farlo.
Jon Skeet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.