266 votes

Meilleure façon de combiner deux ou plusieurs tableaux d’octets 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 mener à bien cette tâche?

351voto

Matt Davis Points 22019

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:

  1. Nouveau Tableau d'Octets à l'aide de System.Array.Copy - 0.2187556 secondes
  2. Nouveau Tableau d'Octets à l'aide de System.Buffer.BlockCopy - 0.1406286 secondes
  3. IEnumerable à l'aide de C# rendement de l'opérateur - 0.0781270 secondes
  4. 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:

  1. Nouveau Tableau d'Octets à l'aide de System.Array.Copy - 0.2812554 secondes
  2. Nouveau Tableau d'Octets à l'aide de System.Buffer.BlockCopy - 0.2500048 secondes
  3. IEnumerable à l'aide de C# rendement de l'opérateur - 0.0625012 secondes
  4. 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:

  1. Nouveau Tableau d'Octets à l'aide de System.Array.Copy - 1.0781457 secondes
  2. Nouveau Tableau d'Octets à l'aide de System.Buffer.BlockCopy - 1.0156445 secondes
  3. IEnumerable à l'aide de C# rendement de l'opérateur - 0.0625012 secondes
  4. 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:

  1. Nouveau Tableau d'Octets à l'aide de System.Array.Copy - 13.4533833 secondes
  2. Nouveau Tableau d'Octets à l'aide de System.Buffer.BlockCopy - 13.1096267 secondes
  3. IEnumerable à l'aide de C# rendement de l'opérateur - 0 secondes
  4. 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:

  1. Nouveau Tableau d'Octets à l'aide de System.Array.Copy - 78.20550510 secondes
  2. Nouveau Tableau d'Octets à l'aide de System.Buffer.BlockCopy - 77.89261900 secondes
  3. IEnumerable à l'aide de C# rendement de l'opérateur - 551.7150161 secondes
  4. 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.

169voto

Jon Skeet Points 692016

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é.

30voto

FryGuy Points 5999

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;
}
 

7voto

Andrew Points 2941

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();
}
 

2voto

edd Points 21
    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;
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X