Eu tenho matrizes de 3 bytes em C # que preciso combinar em uma. Qual seria o método mais eficiente para concluir esta tarefa?
Eu tenho matrizes de 3 bytes em C # que preciso combinar em uma. Qual seria o método mais eficiente para concluir esta tarefa?
Respostas:
Para tipos primitivos (incluindo bytes), use em System.Buffer.BlockCopy
vez 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:
System.Array.Copy
- 0.2187556 segundosSystem.Buffer.BlockCopy
- 0,1406286 segundosAumentei o tamanho de cada matriz para 100 elementos e refiz o teste:
System.Array.Copy
- 0,2812554 segundosSystem.Buffer.BlockCopy
- 0,2500048 segundosAumentei o tamanho de cada matriz para 1000 elementos e refiz o teste:
System.Array.Copy
- 1.0781457 segundosSystem.Buffer.BlockCopy
- 1.0156445 segundosPor fim, aumentei o tamanho de cada matriz para 1 milhão de elementos e refiz o teste, executando cada loop apenas 4000 vezes:
System.Array.Copy
- 13.4533833 segundosSystem.Buffer.BlockCopy
- 13.1096267 segundosPortanto, 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.BlockCopy
soluçã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:
System.Array.Copy
- 78.20550510 segundosSystem.Buffer.BlockCopy
- 77.89261900 segundosO 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.
Muitas das respostas me parecem ignorar os requisitos declarados:
Esses dois juntos descartam uma sequência de bytes LINQ - qualquer coisa com yield
isso 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.
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.
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;
}
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();
}
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();
}
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
), 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).
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();
}
}
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;
}
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;
}
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.
/// <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();
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();
}
}
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();