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?
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?
Respuestas:
Para los tipos primitivos (incluidos los bytes), use en System.Buffer.BlockCopy
lugar 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:
System.Array.Copy
- 0.2187556 segundosSystem.Buffer.BlockCopy
- 0.1406286 segundosAumenté el tamaño de cada matriz a 100 elementos y volví a ejecutar la prueba:
System.Array.Copy
- 0.2812554 segundosSystem.Buffer.BlockCopy
- 0.2500048 segundosAumenté el tamaño de cada matriz a 1000 elementos y volví a ejecutar la prueba:
System.Array.Copy
- 1.0781457 segundosSystem.Buffer.BlockCopy
- 1.0156445 segundosFinalmente, 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:
System.Array.Copy
- 13.4533833 segundosSystem.Buffer.BlockCopy
- 13.1096267 segundosEntonces, 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.BlockCopy
solució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:
System.Array.Copy
- 78.20550510 segundosSystem.Buffer.BlockCopy
- 77.89261900 segundosEl 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.
Muchas de las respuestas me parecen ignorar los requisitos establecidos:
Estos dos juntos descartan una secuencia LINQ de bytes: cualquier cosa yield
que 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.
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.
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;
}
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();
}
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();
}
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
), 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).
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();
}
}
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;
}
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;
}
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.
/// <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();
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();
}
}
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();