Beste Möglichkeit, zwei oder mehr Byte-Arrays in C # zu kombinieren

238

Ich habe 3-Byte-Arrays in C #, die ich zu einem kombinieren muss. Was wäre die effizienteste Methode, um diese Aufgabe zu erledigen?

Superdumbell
quelle
3
Was genau sind Ihre Anforderungen? Nehmen Sie die Vereinigung der Arrays oder behalten Sie mehrere Instanzen desselben Werts bei? Möchten Sie die Elemente sortieren oder möchten Sie die Reihenfolge in den anfänglichen Arrays beibehalten? Suchen Sie Effizienz in Bezug auf Geschwindigkeit oder Codezeilen?
Jason
Ich liebe es, "am besten" hängt von Ihren Anforderungen ab.
Ady
7
Wenn Sie in der Lage sind, LINQ zu verwenden, können Sie einfach die folgende ConcatMethode verwenden:IEnumerable<byte> arrays = array1.Concat(array2).Concat(array3);
casperOne
1
Bitte versuchen Sie, Ihre Fragen klarer zu formulieren. Diese vage Frage hat bei diesen Menschen viel Verwirrung gestiftet, um sich die Zeit zu nehmen, Ihnen zu antworten.
Drew Noakes

Antworten:

325

Verwenden Sie für primitive Typen (einschließlich Bytes) System.Buffer.BlockCopyanstelle von System.Array.Copy. Es ist schneller.

Ich habe jede der vorgeschlagenen Methoden in einer Schleife zeitlich festgelegt, die 1 Million Mal ausgeführt wurde, wobei 3 Arrays mit jeweils 10 Bytes verwendet wurden. Hier sind die Ergebnisse:

  1. Neues Byte-Array mit System.Array.Copy - 0,2187556 Sekunden
  2. Neues Byte-Array mit System.Buffer.BlockCopy - 0.1406286 Sekunden
  3. IEnumerable <Byte> mit dem C # Yield-Operator - 0,0781270 Sekunden
  4. IEnumerable <Byte> mit Concin <> von LINQ - 0,0781270 Sekunden

Ich habe die Größe jedes Arrays auf 100 Elemente erhöht und den Test erneut ausgeführt:

  1. Neues Byte-Array mit System.Array.Copy - 0,2812554 Sekunden
  2. Neues Byte-Array mit System.Buffer.BlockCopy - 0,2500048 Sekunden
  3. IEnumerable <Byte> mit dem C # -Ertragsoperator - 0,0625012 Sekunden
  4. IEnumerable <Byte> mit Concat <> von LINQ -> 0,0781265 Sekunden

Ich habe die Größe jedes Arrays auf 1000 Elemente erhöht und den Test erneut ausgeführt:

  1. Neues Byte-Array mit System.Array.Copy - 1.0781457 Sekunden
  2. Neues Byte-Array mit System.Buffer.BlockCopy - 1.0156445 Sekunden
  3. IEnumerable <Byte> mit dem C # -Ertragsoperator - 0,0625012 Sekunden
  4. IEnumerable <Byte> mit Concat <> von LINQ -> 0,0781265 Sekunden

Schließlich habe ich die Größe jedes Arrays auf 1 Million Elemente erhöht und den Test erneut ausgeführt, wobei jede Schleife nur 4000 Mal ausgeführt wurde:

  1. Neues Byte-Array mit System.Array.Copy - 13.4533833 Sekunden
  2. Neues Byte-Array mit System.Buffer.BlockCopy - 13.1096267 Sekunden
  3. IEnumerable <Byte> mit C # Yield Operator - 0 Sekunden
  4. IEnumerable <Byte> mit LINQs Concat <> - 0 Sekunden

Wenn Sie also ein neues Byte-Array benötigen, verwenden Sie

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

Wenn Sie jedoch eine verwenden können IEnumerable<byte>, bevorzugen Sie definitiv die Concat <> -Methode von LINQ. Es ist nur geringfügig langsamer als der C # -Ertragsoperator, aber prägnanter und eleganter.

IEnumerable<byte> rv = a1.Concat(a2).Concat(a3);

Wenn Sie eine beliebige Anzahl von Arrays haben und .NET 3.5 verwenden, können Sie die System.Buffer.BlockCopyLösung wie folgt allgemeiner gestalten:

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

* Hinweis: Für den obigen Block müssen Sie oben den folgenden Namespace hinzufügen, damit er funktioniert.

using System.Linq;

Zu Jon Skeets Punkt bezüglich der Iteration der nachfolgenden Datenstrukturen (Byte-Array vs. IEnumerable <Byte>) habe ich den letzten Timing-Test (1 Million Elemente, 4000 Iterationen) erneut ausgeführt und eine Schleife hinzugefügt, die mit jedem über das gesamte Array iteriert bestehen:

  1. Neues Byte-Array mit System.Array.Copy - 78.20550510 Sekunden
  2. Neues Byte-Array mit System.Buffer.BlockCopy - 77.89261900 Sekunden
  3. IEnumerable <Byte> mit dem C # Yield-Operator - 551.7150161 Sekunden
  4. IEnumerable <Byte> mit LINQs Concat <> - 448.1804799 Sekunden

Der Punkt ist, dass es SEHR wichtig ist, die Effizienz sowohl der Erstellung als auch der Verwendung der resultierenden Datenstruktur zu verstehen . Wenn Sie sich nur auf die Effizienz der Erstellung konzentrieren, wird möglicherweise die mit der Verwendung verbundene Ineffizienz übersehen. Ein großes Lob, Jon.

Matt Davis
quelle
61
Aber konvertieren Sie es am Ende tatsächlich in ein Array, wie es die Frage erfordert? Wenn nicht, ist es natürlich schneller - aber es erfüllt nicht die Anforderungen.
Jon Skeet
18
Betreff: Matt Davis - Es spielt keine Rolle, ob Ihre "Anforderungen" die IEnumerable in ein Array verwandeln müssen - alles, was Ihre Anforderungen benötigen, ist, dass das Ergebnis tatsächlich in irgendeiner Form verwendet wird . Der Grund, warum Ihre Leistungstests auf IEnumerable so niedrig sind, ist, dass Sie eigentlich nichts tun ! LINQ führt keine seiner Arbeiten aus, bis Sie versuchen, die Ergebnisse zu verwenden. Aus diesem Grund finde ich Ihre Antwort objektiv falsch und könnte andere dazu veranlassen, LINQ zu verwenden, wenn sie dies unbedingt nicht sollten, wenn ihnen die Leistung am Herzen liegt.
Csauve
12
Ich habe die gesamte Antwort einschließlich Ihres Updates gelesen, mein Kommentar steht. Ich weiß, dass ich spät zur Party komme, aber die Antwort ist grob irreführend und die erste Hälfte ist offensichtlich falsch .
Csauve
14
Warum ist die Antwort, die falsche und irreführende Informationen enthält, die am häufigsten gewählte Antwort und wurde so bearbeitet, dass ihre ursprüngliche Aussage im Grunde genommen vollständig ungültig wird, nachdem jemand (Jon Skeet) darauf hingewiesen hat, dass sie nicht einmal die Frage des OP beantwortet hat?
MrCC
3
Irreführende Antwort. Auch die Ausgabe beantwortet die Frage nicht.
Serge Profafilecebook
154

Viele der Antworten scheinen mir die angegebenen Anforderungen zu ignorieren:

  • Das Ergebnis sollte ein Byte-Array sein
  • Es sollte so effizient wie möglich sein

Diese beiden zusammen schließen eine LINQ-Folge von Bytes aus - alles, yieldwas dazu gehört, macht es unmöglich, die endgültige Größe zu erhalten, ohne die gesamte Folge zu durchlaufen.

Wenn dies natürlich nicht die tatsächlichen Anforderungen sind, könnte LINQ eine perfekte Lösung (oder die IList<T>Implementierung) sein. Ich gehe jedoch davon aus, dass Superdumbell weiß, was er will.

(BEARBEITEN: Ich habe gerade einen anderen Gedanken gehabt. Es gibt einen großen semantischen Unterschied zwischen dem Erstellen einer Kopie der Arrays und dem trägen Lesen. Überlegen Sie, was passiert, wenn Sie die Daten in einem der "Quell" -Arrays nach dem Aufrufen des Combine(oder was auch immer) ändern ) Methode, aber bevor das Ergebnis verwendet wird - bei verzögerter Auswertung wird diese Änderung sichtbar. Bei einer sofortigen Kopie wird dies nicht der Fall sein. Unterschiedliche Situationen erfordern unterschiedliches Verhalten - nur etwas, das Sie beachten sollten.)

Hier sind meine vorgeschlagenen Methoden - die denen in einigen anderen Antworten sehr ähnlich sind, sicherlich :)

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

Natürlich erfordert die "params" -Version, dass zuerst ein Array der Byte-Arrays erstellt wird, was zu zusätzlicher Ineffizienz führt.

Jon Skeet
quelle
Jon, ich verstehe genau, was du sagst. Mein einziger Punkt ist, dass manchmal Fragen mit einer bestimmten Implementierung gestellt werden, ohne bereits zu bemerken, dass andere Lösungen existieren. Nur eine Antwort zu geben, ohne Alternativen anzubieten, scheint mir ein schlechter Dienst zu sein. Gedanken?
Matt Davis
1
@Matt: Ja, Alternativen anzubieten ist gut - aber es lohnt sich zu erklären, dass es sich um Alternativen handelt, anstatt sie als Antwort auf die gestellte Frage weiterzugeben. (Ich sage nicht, dass Sie das getan haben - Ihre Antwort ist sehr gut.)
Jon Skeet
4
(Obwohl ich denke, dass Ihr Leistungsbenchmark die Zeit anzeigen sollte, die benötigt wird, um alle Ergebnisse in jedem Fall durchzugehen, um zu vermeiden, dass eine faule Bewertung einen unfairen Vorteil erhält.)
Jon Skeet
1
Selbst ohne die Anforderung "Ergebnis muss ein Array sein" zu erfüllen, würde die einfache Erfüllung der Anforderung "Ergebnis muss in gewisser Weise verwendet werden" LINQ nicht optimal machen. Ich denke, dass die Anforderung, das Ergebnis verwenden zu können, implizit sein sollte!
Csauve
2
@andleer: Abgesehen von allem anderen funktioniert Buffer.BlockCopy nur mit primitiven Typen.
Jon Skeet
44

Ich habe Matts LINQ-Beispiel für die Code-Sauberkeit noch einen Schritt weiter gebracht:

byte[] rv = a1.Concat(a2).Concat(a3).ToArray();

In meinem Fall sind die Arrays klein, daher mache ich mir keine Sorgen um die Leistung.

Nate Barbettini
quelle
3
Kurze und einfache Lösung, ein Leistungstest wäre toll!
Sebastian
3
Dies ist definitiv klar, lesbar, erfordert keine externen Bibliotheken / Helfer und ist in Bezug auf die Entwicklungszeit sehr effizient. Großartig, wenn die Laufzeitleistung nicht kritisch ist.
Binki
28

Wenn Sie einfach ein neues Byte-Array benötigen, verwenden Sie Folgendes:

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

Wenn Sie nur eine einzige IEnumerable benötigen, können Sie alternativ den C # 2.0-Ertragsoperator verwenden:

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;
}
FryGuy
quelle
Ich habe etwas Ähnliches wie Ihre zweite Option zum Zusammenführen großer Streams getan, das wie ein Zauber funktioniert hat. :)
Greg D
2
Die zweite Option ist großartig. +1.
R. Martinho Fernandes
10

Ich bin tatsächlich auf einige Probleme bei der Verwendung von Concat gestoßen ... (bei Arrays im Wert von 10 Millionen ist es tatsächlich abgestürzt).

Ich fand das Folgende einfach, leicht und funktioniert gut genug, ohne auf mich zu stürzen, und es funktioniert für JEDE Anzahl von Arrays (nicht nur drei) (es verwendet LINQ):

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

Die Memorystream-Klasse macht diesen Job ziemlich gut für mich. Ich konnte die Pufferklasse nicht so schnell wie Memorystream laufen lassen.

using (MemoryStream ms = new MemoryStream())
{
  ms.Write(BitConverter.GetBytes(22),0,4);
  ms.Write(BitConverter.GetBytes(44),0,4);
  ms.ToArray();
}
Andrew
quelle
3
Wie bereits erwähnt, habe ich 10.000.000 Mal einen Test in einer Schleife durchgeführt, und MemoryStream war 290% langsamer als Buffer.BlockCopy
esac
In einigen Fällen iterieren Sie möglicherweise über eine Reihe von Arrays, ohne die einzelnen Array-Längen vorher zu kennen. Dies funktioniert in diesem Szenario gut. BlockCopy setzt voraus, dass ein Zielarray vorgefertigt wird
Sentinel
Wie @Sentinel sagte, ist diese Antwort perfekt für mich, da ich keine Kenntnis über die Größe der Dinge habe, die ich schreiben muss, und ich die Dinge sehr sauber machen kann. Es spielt sich auch gut mit .NET Core 3's [ReadOnly] Span <byte>!
Wasser
Wenn Sie MemoryStream mit der endgültigen Größe der Größe initialisieren, wird es nicht neu erstellt und ist schneller @esac.
Tono Nam
2
    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;
    }
edd
quelle
Leider funktioniert dies nicht bei allen Typen. Marshal.SizeOf () kann für viele Typen keine Größe zurückgeben (versuchen Sie, diese Methode mit Arrays von Zeichenfolgen zu verwenden, und es wird eine Ausnahme angezeigt: "Typ 'System.String' kann nicht als nicht verwaltete Struktur gemarshallt werden; keine sinnvolle Größe oder Offset kann berechnet werden ". Sie könnten versuchen, den Typparameter nur auf Referenztypen zu beschränken (durch Hinzufügen where T : struct), aber da ich kein Experte für die Innereien der CLR bin, kann ich nicht sagen, ob Sie möglicherweise auch Ausnahmen für bestimmte Strukturen erhalten (zB wenn sie Referenztypfelder enthalten).
Daniel Scott
2
    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();
        }
    }
Peter Ertl
quelle
Ihre Antwort könnte besser sein, wenn Sie eine kleine Erklärung zu diesem Codebeispiel veröffentlicht hätten.
Nach dem
1
Es verkettet ein Array von Byte-Arrays zu einem großen Byte-Array (wie folgt): [1,2,3] + [4,5] + [6,7] ==> [1,2,3,4,5 , 6,7]
Peter Ertl
1

Kann Generika verwenden, um Arrays zu kombinieren. Der folgende Code kann einfach auf drei Arrays erweitert werden. Auf diese Weise müssen Sie niemals Code für verschiedene Arten von Arrays duplizieren. Einige der oben genannten Antworten erscheinen mir zu komplex.

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;
    }
BajaPaul
quelle
0

Hier ist eine Verallgemeinerung der Antwort von @Jon Skeet. Es ist im Grunde das gleiche, nur ist es für jede Art von Array verwendbar, nicht nur für 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;
}
o_c
quelle
3
ACHTUNG! Diese Methoden funktionieren bei keinem Array-Typ mit Elementen, die länger als ein Byte sind (so ziemlich alles andere als Byte-Arrays). Buffer.BlockCopy () arbeitet mit Mengen von Bytes, nicht mit der Anzahl von Array-Elementen. Der Grund, warum es mit einem Byte-Array leicht verwendet werden kann, besteht darin, dass jedes Element des Arrays ein einzelnes Byte ist, sodass die physische Länge des Arrays der Anzahl der Elemente entspricht. Um Johns Byte [] -Methoden in generische Methoden umzuwandeln, müssen Sie alle Offsets und Längen mit der Bytelänge eines einzelnen Array-Elements multiplizieren. Andernfalls werden nicht alle Daten kopiert.
Daniel Scott
2
Normalerweise berechnen Sie damit die Größe eines einzelnen Elements sizeof(...)und multiplizieren diese mit der Anzahl der Elemente, die Sie kopieren möchten. Sizeof kann jedoch nicht mit einem generischen Typ verwendet werden. Bei einigen Typen ist die Verwendung möglich Marshal.SizeOf(typeof(T)), bei bestimmten Typen (z. B. Zeichenfolgen) treten jedoch Laufzeitfehler auf. Jemand, der das Innenleben von CLR-Typen genauer kennt, kann hier auf alle möglichen Fallen hinweisen. Es genügt zu sagen, dass das Schreiben einer generischen Array-Verkettungsmethode [mit BlockCopy] nicht trivial ist.
Daniel Scott
2
Und schließlich können Sie eine generische Array-Verkettungsmethode wie diese fast genau wie oben gezeigt (mit etwas geringerer Leistung) schreiben, indem Sie stattdessen Array.Copy verwenden. Ersetzen Sie einfach alle Buffer.BlockCopy-Aufrufe durch Array.Copy-Aufrufe.
Daniel Scott
0
    /// <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();
Mehmet ÜNLÜ
quelle
Vielen Dank für den Beitrag. Da es bereits vor über einem Jahrzehnt eine Reihe hoch bewerteter Antworten darauf gibt, wäre es hilfreich, eine Erklärung zu geben, was Ihren Ansatz auszeichnet. Warum sollte jemand dies anstelle der akzeptierten Antwort verwenden?
Jeremy Caney
Ich benutze gerne erweiterte Methoden, weil es klaren Code zu verstehen gibt. Dieser Code wählt zwei Arrays mit Startindex und count und concat aus. Auch diese Methode wurde erweitert. Das ist also für alle Array-Typen, die für alle Zeiten bereit sind
Mehmet ÜNLÜ
Das macht für mich Sinn! Haben Sie etwas dagegen, Ihre Frage so zu bearbeiten, dass sie diese Informationen enthält? Ich denke, es wäre für zukünftige Leser wertvoll, dies im Voraus zu haben, damit sie Ihren Ansatz schnell von den vorhandenen Antworten unterscheiden können. Danke dir!
Jeremy Caney
-1

Alles, was Sie brauchen, um eine Liste von Byte-Arrays zu übergeben, und diese Funktion gibt Ihnen das Array von Bytes (zusammengeführt) zurück. Dies ist die beste Lösung, denke ich :).

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

Concat ist die richtige Antwort, aber aus irgendeinem Grund erhält eine handgerollte Sache die meisten Stimmen. Wenn Ihnen diese Antwort gefällt, möchten Sie diese allgemeinere Lösung vielleicht noch mehr:

    IEnumerable<byte> Combine(params byte[][] arrays)
    {
        foreach (byte[] a in arrays)
            foreach (byte b in a)
                yield return b;
    }

was Sie Dinge tun lassen würde wie:

    byte[] c = Combine(new byte[] { 0, 1, 2 }, new byte[] { 3, 4, 5 }).ToArray();
Mark Maxham
quelle
5
Die Frage fragt speziell nach der effizientesten Lösung. Enumerable.ToArray wird nicht sehr effizient sein, da es zunächst nicht wissen kann, wie groß das endgültige Array ist - wohingegen die handgerollten Techniken dies können.
Jon Skeet