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 effectuer cette tâche?
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 effectuer cette tâche?
Réponses:
Pour les types primitifs (y compris les octets), utilisez System.Buffer.BlockCopy
plutôt que System.Array.Copy
. C'est plus rapide.
J'ai chronométré chacune des méthodes suggérées dans une boucle exécutée 1 million de fois en utilisant 3 tableaux de 10 octets chacun. Voici les résultats:
System.Array.Copy
- 0,2187556 secondesSystem.Buffer.BlockCopy
- 0,1406286 secondesJ'ai augmenté la taille de chaque tableau à 100 éléments et relancé le test:
System.Array.Copy
- 0,2812554 secondesSystem.Buffer.BlockCopy
- 0,2500048 secondesJ'ai augmenté la taille de chaque tableau à 1000 éléments et relancé le test:
System.Array.Copy
- 1.0781457 secondesSystem.Buffer.BlockCopy
- 1.0156445 secondesEnfin, j'ai augmenté la taille de chaque tableau à 1 million d'éléments et relancé le test, exécutant chaque boucle seulement 4000 fois:
System.Array.Copy
- 13.4533833 secondesSystem.Buffer.BlockCopy
- 13.1096267 secondesDonc, 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 une IEnumerable<byte>
, préférez DEFINITIVEMENT la méthode Concat <> de LINQ. Il n'est que légèrement plus lent que l'opérateur de rendement C #, mais il est plus concis et plus élégant.
IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);
Si vous avez un nombre arbitraire de tableaux et utilisez .NET 3.5, vous pouvez rendre la 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;
}
* Remarque: Le bloc ci-dessus nécessite que vous ajoutiez l'espace de noms suivant en haut pour qu'il fonctionne.
using System.Linq;
Au point de Jon Skeet concernant l'itération des structures de données suivantes (tableau d'octets vs IEnumerable <byte>), j'ai réexécuté le dernier test de synchronisation (1 million d'éléments, 4000 itérations), en ajoutant une boucle qui itère sur le tableau complet avec chaque passer:
System.Array.Copy
- 78.20550510 secondesSystem.Buffer.BlockCopy
- 77.89261900 secondesLe fait est qu'il est TRÈS important de comprendre l'efficacité de la création et de l'utilisation de la structure de données résultante. Se concentrer simplement sur l'efficacité de la création peut ignorer l'inefficacité associée à l'utilisation. Bravo, Jon.
Beaucoup de réponses me semblent ignorer les exigences énoncées:
Ensemble, ces deux éléments excluent une séquence d'octets LINQ - tout ce qui yield
contient rendra impossible l'obtention de la taille finale sans parcourir toute la séquence.
Si ce ne sont pas les vraies exigences bien sûr, LINQ pourrait être une bonne solution (ou l' IList<T>
implémentation). Cependant, je suppose que Superdumbell sait ce qu'il veut.
(EDIT: je viens d'avoir une autre pensée. Il y a une grande différence sémantique entre faire une copie des tableaux et les lire paresseusement. Réfléchissez à ce qui se passe si vous modifiez les données dans l'un des tableaux "source" après avoir appelé le Combine
(ou quoi que ce soit) ) mais avant d'utiliser le résultat - avec une évaluation paresseuse, ce changement sera visible. Avec une copie immédiate, il ne le sera pas. Différentes situations nécessiteront un comportement différent - juste quelque chose à savoir.)
Voici mes méthodes proposées - qui sont très similaires à celles contenues dans certaines des 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, la version "params" nécessite de créer d'abord un tableau des tableaux d'octets, ce qui introduit une inefficacité supplémentaire.
J'ai pris l'exemple LINQ de Matt un peu plus loin pour la propreté du code:
byte[] rv = a1.Concat(a2).Concat(a3).ToArray();
Dans mon cas, les tableaux sont petits, donc je ne suis pas préoccupé par les performances.
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;
}
Alternativement, si vous avez juste 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;
}
J'ai en fait rencontré des problèmes avec Concat ... (avec des tableaux dans les 10 millions, il s'est en fait écrasé).
J'ai trouvé que ce qui suit était simple, facile et fonctionne assez bien sans tomber en panne sur moi, et cela fonctionne pour N'IMPORTE QUEL nombre de tableaux (pas seulement trois) (Il utilise LINQ):
public static byte[] ConcatByteArrays(params byte[][] arrays)
{
return arrays.SelectMany(x => x).ToArray();
}
La classe memorystream fait très bien ce travail pour moi. Je n'ai pas pu faire fonctionner la classe tampon 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;
}
where T : struct
), mais - n'étant pas un expert dans les entrailles du CLR - je ne pourrais pas dire si vous pourriez également obtenir des exceptions sur certaines structures (par exemple, s'ils contiennent des champs de type référence)
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();
}
}
Peut utiliser des génériques pour combiner des tableaux. Le code suivant peut facilement être étendu à trois tableaux. De cette façon, vous n'avez jamais besoin de dupliquer le code pour différents types de tableaux. Certaines des réponses ci-dessus me semblent trop complexes.
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;
}
Voici une généralisation de la réponse fournie par @Jon Skeet. C'est fondamentalement le même, seulement il est utilisable pour tout type de tableau, pas seulement les octets:
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(...)
et multiplier cela par le nombre d'éléments que vous souhaitez copier, mais sizeof ne peut pas être utilisé avec un type générique. Il est possible - pour certains types - d'utiliser Marshal.SizeOf(typeof(T))
, mais vous obtiendrez des erreurs d'exécution avec certains types (par exemple des chaînes). Quelqu'un avec une connaissance plus approfondie du fonctionnement interne des types CLR sera en mesure de signaler tous les pièges possibles ici. Autant dire que l'écriture d'une méthode de concaténation de tableau générique [à l'aide de BlockCopy] n'est pas anodine.
/// <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();
Tout ce dont vous avez besoin pour passer la liste des tableaux d'octets et cette fonction vous renverra le tableau d'octets (fusionné). C'est la meilleure solution, je pense :).
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 est la bonne réponse, mais pour une raison quelconque, une chose contrôlée manuellement obtient le plus de votes. Si vous aimez cette réponse, peut-être aimeriez-vous encore plus cette solution plus générale:
IEnumerable<byte> Combine(params byte[][] arrays)
{
foreach (byte[] a in arrays)
foreach (byte b in a)
yield return b;
}
qui vous permettrait de faire des choses comme:
byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();