Ho array di 3 byte in C # che devo combinare in uno. Quale sarebbe il metodo più efficiente per completare questa attività?
Ho array di 3 byte in C # che devo combinare in uno. Quale sarebbe il metodo più efficiente per completare questa attività?
Risposte:
Per i tipi primitivi (inclusi i byte), utilizzare System.Buffer.BlockCopy
invece 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:
System.Array.Copy
- 0,2187556 secondiSystem.Buffer.BlockCopy
- 0,1406286 secondiHo aumentato la dimensione di ciascun array a 100 elementi e rieseguito il test:
System.Array.Copy
- 0,2812554 secondiSystem.Buffer.BlockCopy
- 0,2500048 secondiHo aumentato la dimensione di ciascun array a 1000 elementi e ho eseguito nuovamente il test:
System.Array.Copy
- 1.0781457 secondiSystem.Buffer.BlockCopy
- 1,0156445 secondiInfine, ho aumentato le dimensioni di ciascun array a 1 milione di elementi e ho eseguito nuovamente il test, eseguendo ogni ciclo solo 4000 volte:
System.Array.Copy
- 13,4533833 secondiSystem.Buffer.BlockCopy
- 13.1096267 secondiQuindi, 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.BlockCopy
soluzione 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:
System.Array.Copy
- 78.20550510 secondiSystem.Buffer.BlockCopy
- 77,89261900 secondiIl 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.
Molte delle risposte mi sembrano ignorare i requisiti dichiarati:
Questi due insieme escludono una sequenza di byte LINQ - qualsiasi cosa yield
renderà 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.
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.
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;
}
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();
}
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();
}
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;
}
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)
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();
}
}
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;
}
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;
}
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.
/// <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();
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();
}
}
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();