web-dev-qa-db-fra.com

Meilleure façon de combiner deux tableaux d’octets ou plus en C #

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?

225
Superdumbell

Pour les types primitifs (y compris les octets), utilisez System.Buffer.BlockCopy au lieu de 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:

  1. Nouveau tableau d'octets utilisant _System.Array.Copy_ - 0,2187556 secondes
  2. Nouveau tableau d'octets utilisant _System.Buffer.BlockCopy_ - 0.1406286 secondes
  3. IEnumerable <byte> utilisant l'opérateur de rendement C # - 0.0781270 secondes
  4. IEnumerable <byte> en utilisant la concat <> de LINQ -> 0.0781270 secondes

J'ai augmenté la taille de chaque tableau à 100 éléments et j'ai relancé le test:

  1. Nouveau tableau d'octets utilisant _System.Array.Copy_ - 0,2812554 secondes
  2. Nouveau tableau d'octets utilisant _System.Buffer.BlockCopy_ - 0,2500048 secondes
  3. IEnumerable <byte> utilisant l'opérateur de rendement C # - 0.0625012 secondes
  4. IEnumerable <byte> en utilisant la concat <> de LINQ -> 0.0781265 secondes

J'ai augmenté la taille de chaque tableau à 1000 éléments et j'ai relancé le test:

  1. Nouveau tableau d'octets utilisant _System.Array.Copy_ - 1,0781457 secondes
  2. Nouveau tableau d'octets utilisant _System.Buffer.BlockCopy_ - 1,0156445 secondes
  3. IEnumerable <byte> utilisant l'opérateur de rendement C # - 0.0625012 secondes
  4. IEnumerable <byte> en utilisant la concat <> de LINQ -> 0.0781265 secondes

Enfin, j'ai augmenté la taille de chaque tableau à 1 million d'éléments et j'ai relancé le test en exécutant chaque boucle seulement 4000 fois:

  1. Nouveau tableau d'octets utilisant _System.Array.Copy_ - 13,4533833 secondes
  2. Nouveau tableau d'octets utilisant _System.Buffer.BlockCopy_ - 13,1096267 secondes
  3. IEnumerable <byte> utilisant l'opérateur de rendement C # - 0 secondes
  4. IEnumerable <byte> utilisant la concat <> de LINQ -> 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>_, DÉFINITIVEMENT préférez LINQ's Concat < > méthode. 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 solution _System.Buffer.BlockCopy_ 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 l'ajout de l'espace de nom suivant en haut pour que cela fonctionne.

_using System.Linq;
_

Au point de Jon Skeet concernant l'itération des structures de données suivantes (tableau d'octets contre IEnumerable <octet>), j'ai relancé le dernier test de synchronisation (1 million d'éléments, 4000 itérations), en ajoutant une boucle qui itère sur le tableau complet à chaque fois. passer:

  1. Nouveau tableau d'octets utilisant _System.Array.Copy_ - 78,20550510 secondes
  2. Nouveau tableau d'octets utilisant _System.Buffer.BlockCopy_ - 77,89261900 secondes
  3. IEnumerable <byte> utilisant l'opérateur de rendement C # - 551.7150161 secondes
  4. IEnumerable <byte> en utilisant la concat <> de LINQ -> 448.1804799 secondes

Le point est, il est TRÈS important de comprendre l'efficacité de la création et l'utilisation de la structure de données résultante. Se concentrer simplement sur l'efficacité de la création peut faire oublier l'inefficacité associée à l'utilisation. Bravo, Jon.

313
Matt Davis

Beaucoup de réponses me semblent ignorer les exigences énoncées:

  • Le résultat devrait être un tableau d'octets
  • Il devrait être aussi efficace que possible

Ces deux éléments ensemble excluent une séquence d'octets LINQ - tout ce qui a pour valeur yield rendra impossible l'obtention de la taille finale sans parcourir la séquence entière.

Si ce ne sont bien sûr pas les réelles exigences, LINQ pourrait être une solution tout à fait appropriée (ou la mise en oeuvre IList<T>.). Cependant, je suppose que Superdumbell sait ce qu'il veut.

(EDIT: Je viens d’avoir une autre pensée. Il ya une grande différence sémantique entre faire une copie des tableaux et les lire paresseusement. Considérez ce qui se passe si vous modifiez les données dans l’un des tableaux "source" après avoir appelé le Combine (ou quelque méthode que ce soit), mais avant d’utiliser le résultat - avec une évaluation paresseuse, ce changement sera visible. Avec une copie immédiate, ce ne sera pas le cas.

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 de tableaux d'octets, ce qui introduit une inefficacité supplémentaire.

144
Jon Skeet

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, je ne m'inquiète donc pas des performances.

35
Nate Barbettini

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 n'avez besoin que 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;
}
27
FryGuy

En fait, j'ai rencontré des problèmes avec l'utilisation de Concat ...

J'ai trouvé que les éléments suivants étaient simples, faciles et fonctionnent assez bien sans me planter dessus, et cela fonctionne pour TOUT nombre de tableaux (pas seulement trois) (il utilise LINQ):

public static byte[] ConcatByteArrays(params byte[][]  arrays)
{
    return arrays.SelectMany(x => x).ToArray();
}
10
00jt

La classe memorystream fait ce travail plutôt bien pour moi. Je ne pouvais pas faire en sorte que la classe de mémoire tampon s'exécute aussi rapidement que memorystream.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}
5
Andrew
    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;
    }
2
edd
    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();
        }
    }
2
Peter Ertl

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 du code pour différents types de tableaux. Certaines des réponses ci-dessus me paraissent excessivement 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;
    }
1
BajaPaul

Voici une généralisation de la réponse fournie par @Jon Skeet. C'est fondamentalement la même chose, seulement elle 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;
}
0
o_c