J'ai des tableaux de 3 octets en C # que je dois combiner en un seul. Quelle serait la méthode la plus efficace pour mener à bien cette tâche?
Réponses
Trop de publicités?Pour les types primitifs (y compris octets), utilisez System.Buffer.BlockCopy
au lieu de System.Array.Copy
. C'est plus rapide.
J'ai chronométré chacune des méthodes proposées dans une boucle exécutée 1 million de fois à l'aide de 3 tableaux de 10 octets chacun. Voici les résultats:
- Nouveau Tableau d'Octets à l'aide de
System.Array.Copy
- 0.2187556 secondes - Nouveau Tableau d'Octets à l'aide de
System.Buffer.BlockCopy
- 0.1406286 secondes - IEnumerable à l'aide de C# rendement de l'opérateur - 0.0781270 secondes
- IEnumerable à l'aide de Linq Concat<> - 0.0781270 secondes
J'ai augmenté la taille de chaque tableau de 100 éléments et re-couru le test:
- Nouveau Tableau d'Octets à l'aide de
System.Array.Copy
- 0.2812554 secondes - Nouveau Tableau d'Octets à l'aide de
System.Buffer.BlockCopy
- 0.2500048 secondes - IEnumerable à l'aide de C# rendement de l'opérateur - 0.0625012 secondes
- IEnumerable à l'aide de Linq Concat<> - 0.0781265 secondes
J'ai augmenté la taille de chaque tableau pour les éléments de 1000 et re-couru le test:
- Nouveau Tableau d'Octets à l'aide de
System.Array.Copy
- 1.0781457 secondes - Nouveau Tableau d'Octets à l'aide de
System.Buffer.BlockCopy
- 1.0156445 secondes - IEnumerable à l'aide de C# rendement de l'opérateur - 0.0625012 secondes
- IEnumerable à l'aide de Linq Concat<> - 0.0781265 secondes
Enfin, j'ai augmenté la taille de chaque tableau à 1 millions d'éléments et de re-couru le test, l'exécution de chaque boucle de seulement 4000 fois:
- Nouveau Tableau d'Octets à l'aide de
System.Array.Copy
- 13.4533833 secondes - Nouveau Tableau d'Octets à l'aide de
System.Buffer.BlockCopy
- 13.1096267 secondes - IEnumerable à l'aide de C# rendement de l'opérateur - 0 secondes
- IEnumerable à l'aide de Linq Concat<> - 0 secondes
Donc, si vous avez besoin d'un nouveau tableau d'octets, utilisez
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 );
Mais, si vous pouvez utiliser un IEnumerable<byte>
, CERTAINEMENT préfèrent Linq Concat<> méthode. C'est seulement légèrement plus lent que le C#, le rendement de l'opérateur, mais elle est plus concise et plus élégant.
IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);
Si vous avez un nombre arbitraire de tableaux et d'utilisation .NET 3.5, vous pouvez faire de l' System.Buffer.BlockCopy
solution plus générique comme ceci:
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;
}
EDIT: Pour Jon Skeet du point relatif à l'itération des structures de données (tableau d'octets vs IEnumerable), j'ai relancé le dernier test de chronométrage (1 millions d'éléments, 4000 itérations), l'ajout d'une boucle pour parcourir le tableau complet à chaque passage:
- Nouveau Tableau d'Octets à l'aide de
System.Array.Copy
- 78.20550510 secondes - Nouveau Tableau d'Octets à l'aide de
System.Buffer.BlockCopy
- 77.89261900 secondes - IEnumerable à l'aide de C# rendement de l'opérateur - 551.7150161 secondes
- IEnumerable à l'aide de Linq Concat<> - 448.1804799 secondes
Le point est, il est TRÈS important de comprendre l'efficacité à la fois de la création et de l'utilisation de la structure de données. Simplement en se concentrant sur l'efficacité de la création peut négliger l'inefficacité liés à l'utilisation. Bravo, Jon.
Beaucoup de réponses me semblent ignorer les exigences énoncées:
- Le résultat devrait être un tableau d'octets
- Il doit être aussi efficace que possible
Ensemble, les deux écarter une LINQ séquence d'octets quoi que ce soit avec yield
est impossible à obtenir la taille finale sans itération sur l'ensemble de la séquence.
Si ce ne sont pas les véritables exigences de cours, LINQ pourrait être une bonne solution (ou l' IList<T>
mise en œuvre). Cependant, je vais supposer que Superdumbell sait ce qu'il veut.
(EDIT: j'ai juste eu une autre pensée. Il y a une grosse différence sémantique entre faire une copie de tableaux et de les lire paresseusement. Examinons ce qui se passe si vous modifiez les données dans un de la "source" des tableaux après l'appel de l' Combine
(ou autre) méthode mais avant d'utiliser le résultat d'évaluation différée, que le changement sera visible. Avec une copie immédiate, il ne sera pas. Différentes situations appel à des comportements différents - juste quelque chose à connaître.)
Voici mes méthodes proposées - qui sont très similaires à celles contenues dans les autres réponses, certainement :)
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;
}
Bien sûr, le "params" version nécessite la création d'un tableau de tableaux d'octets d'abord, qui introduit supplémentaire à l'inefficacité.
Si vous avez simplement besoin d'un nouveau tableau d'octets, utilisez ce qui suit:
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;
}
Si vous avez simplement besoin d'un seul IEnumerable, envisagez d'utiliser l'opérateur de rendement 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;
}
La classe memorystream fait ce travail assez bien pour moi. Je ne pouvais pas faire en sorte que la classe de mémoire tampon fonctionne aussi vite que memorystream.
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;
}