Read byte[] structure

  • C#
  • .NET (FX) 1.0–2.0

Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    Read byte[] structure

    Moin,

    Ich bräuchte mal etwas hilfe wie ich ein bytearray mit folgender struktur vernünftig auslesen und in zwei arrays laden kann:

    Ich habe ein Input array das wie folgt strukturiert ist.

    Quellcode

    1. <keylength><key><data><keylength><key><data> <keylength><key><data> ...

    Das erste byte gibt die länge des darauffolgenden keys an.
    Die danach folgenden bytes definieren den key, die länge die wir davor ausgelesen haben wird benötigt um den key zu lesen. danach kommen
    die eigentlichen Daten, - diese sind IMMER 4 bytes lang.
    Danach wiederholt sich das für die nächten key-daten sätze..

    Ich möchte nun, so schnell wie möglich via buffer.blockcopy oder ähnlichem das ganze in eine list of array laden, wobei jedes array in der liste immer key+data enthält.
    Wie mache ich das am geschicktesten, möglichst performance effizient?
    C# Developer
    Learning C++
    @Rikudo Kannst Du mal ein paar solche Daten posten?
    Hast Du bereits eine andere funktionierende Lösung?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hi @Rikudo!
    Das ganze lässt sich eigentlich recht einfach auf zwei verschiedene Arten lösen:
    1.) Entweder du liest den gesamten Inhalt ein (über die File Klasse aus dem Namespace System.IO), zerlegst den String per String.Substring(auslesen der Keylänge, lesen des Blocks aus Key und Daten - das ganze halt in einer Schleife) oder
    2.) du holst dir die Daten stückchenweise per FileStream (und einem StreamReader) und liest immer so viel ein, wie der Key lang ist, den speicherst du dann in einem Buffer (Puffer Array) anschließend liest du noch 4 bytes für die Daten. Das ganze dann bis du am Ende bist

    Rikudo schrieb:

    Das erste byte gibt die länge des darauffolgenden keys an.

    Rikudo schrieb:

    Die danach folgenden bytes definieren den key, die länge die wir davor ausgelesen haben wird benötigt um den key zu lesen. danach kommen

    Rikudo schrieb:

    die eigentlichen Daten, - diese sind IMMER 4 bytes lang.
    Hier hast du dann die Information wie viele Zeichen du mit FileStream.Read(byte[], int, int) lesen musst

    Rikudo schrieb:

    Danach wiederholt sich das für die nächten key-daten sätze..
    Wie gesagt: Das ganze in einer Schleife machen, die solange geht, bis du am Ende der Datei angelangt bist (falls du das ganze aus einer Datei ausliest - ansonsten allgemein gesagt: Bis der Input zu Ende ist)

    Rikudo schrieb:

    eine list of array laden
    ? Was soll das sein? List(Of Array)?? Ich nehme mal an du wolltest sagen eine List(Of Byte()).

    Rikudo schrieb:

    liste immer key+data enthält.
    Verwende keine List(Of T()), nimm lieber ein Dictionary(Of TKey, TValue). Hier kannst du dann viel bequemer auf die Elemente (per Linq) zugreifen und es ist typsicher. Dazu einfach ein Dictionary(Of Byte(), Byte()) defnieren und nach jedem auslesen per .Add() die Daten hinzufügen.

    Lg Radinator
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Radinator“ ()

    Hi
    ich denke, die Frage ist, welche Anforderungen du an die Performance stellst. Radinators Vorschlag erzeugt recht viele Instanzen, ich weiß nicht, ob das wirklich nötig ist.

    Eine Idee für den allgemeinen Fall wäre, anfangs einfach mal alle Daten einzulesen und das gesamte Array durchzugehen und die Offsets der Daten in eine List(Of Integer) zu speichern, sodass du halt dann auf das i-te Element zugreifen kannst, indem du die Daten ab dem jeweiligen Offset auswertest. Anschließend legst du ein weiteres Array an, das die Schlüssel-Daten-Paare enthält (null steht für "noch nicht ausgelesen" und wird halt beim ersten Aufruf evaluiert und zugewiesen, d.h. der Eintrag sollte ein nullable struct oder eine Klasse sein).

    Wenn alle Elemente nur einmal durchgegangen werden sollen, ist das aber geringfügig ineffizienter, als einfach stupide alle Elemente durchzugehen. Da stehen dir wiederum z.B. Zeiger in einem unsafe-Kontext zur Verfügung. Encoding hat auch Zeigerunterstützung, sollte es sich um String-Schlüssel handeln.

    Allgemein gilt auch, dass Instantiierung im Heap (--> Gilt auch insbesondere für Klassen) recht ineffizient ist, bzw. zumindest weit teurer, als Arrayeinträge zu setzen, usw. D.h. das sollte man in Betracht ziehen. Die Array-Erzeugung selbst ist zwar teuer (und die Neuskalierung sogar noch teurer), aber es ist auch eine recht gute Struktur, um Daten zu halten. Außerdem findet die Erzeugung eben nur einmal statt, was wesentlich effizienter ist, als viele Instantiierungen.

    Viele Grüße
    ~blaze~
    @~blaze~

    ~blaze~ schrieb:

    Radinators Vorschlag erzeugt recht viele Instanzen, ich weiß nicht, ob das wirklich nötig ist.
    Naja nur wenn man die zweite, von mir vorgeschlagene Variante, in Betracht zieht.

    Die erste Variante könnte man natürlich auch noch sparender machen, in dem man den Input nicht als String, sondern - ich geh mal aufgrund des Threadtitel davon aus - als Byte Array einliest. Diese kann mann dann schön durchiteieren, den Enumerator entsprechend setzen und die benötigten Daten auslesen. Ist halt ein weinig komplexer aber machbar.
    In general (across programming languages), a pointer is a number that represents a physical location in memory. A nullpointer is (almost always) one that points to 0, and is widely recognized as "not pointing to anything". Since systems have different amounts of supported memory, it doesn't always take the same number of bytes to hold that number, so we call a "native size integer" one that can hold a pointer on any particular system. - Sam Harwell
    Ich ging in beiden Fällen dann btw. auch davon aus, dass die Daten bereits als ein alle Daten auf einmal enthaltendes Byte-Array vorliegen.
    Ich würde gemischte Daten (Strings + Binärdaten in einer Datei) eher mit BinaryReader oder einem eigenen Reader, d.h. Zeiger, usw., auslesen. Da BinaryReader ineffizienter ist, als es direkt über Zeiger zu lösen, war dann die naheliegendste Lösung eben das. ;)

    Viele Grüße
    ~blaze~
    Hallöle!

    Das Thema hat ein wenig mein Interesse geweckt und ich habe mir hier mal was überlegt, weiß jedoch nicht, inwiefern das sinnvoll (-sinnlos) bzw. effizient (- ineffizient) ist.
    Man kann ja das Bytearray was man bekommt an eine Funktion übergeben.
    Zudem ist die Struktur des ByteArrays durch den TE vorgegeben. kann ich da nicht mit dem Array-Index arbeiten um Länge / Key / Data aufzuarbeiten und diese in einer Beliebigen Form zurück geben?

    ca. so:


    VB.NET-Quellcode

    1. Function GetKeyDataVal() 'As was du willst
    2. Dim nLeng As Integer
    3. Dim sKey As String = ""
    4. Dim sData As String = ""
    5. nLeng = CInt(bArray.GetValue(0))
    6. For i = 1 To nLeng
    7. sKey = sKey & bArray.GetValue(i)
    8. Next
    9. For i = nLeng + 1 To bArray.Length - 1
    10. sData = sData & bArray.GetValue(i)
    11. Next
    12. Return 'was du willst
    13. End function


    richtiger Gedankenansatz oder voll aufm Holzweg??


    MfG Acr0most
    Wenn das Leben wirklich nur aus Nullen und Einsen besteht, dann laufen sicherlich genügen Nullen frei herum. :D
    Signature-Move 8o
    kein Problem mit privaten Konversationen zu Thema XY :thumbup:
    Ich hatte mir eher sowas vorgestellt:
    Spoiler anzeigen

    C#-Quellcode

    1. static unsafe void GetValues(byte[] data, ICollection<KeyValuePair<string, int>> items)
    2. {
    3. if (data == null)
    4. throw new ArgumentNullException(nameof(data));
    5. if (items == null)
    6. throw new ArgumentNullException(nameof(items));
    7. var encoding = System.Text.Encoding.Unicode;
    8. fixed (byte* dataptr = data)
    9. {
    10. byte* dataptrend = dataptr + data.Length; //Ende des Puffers für schnelle Vergleiche zwischenspeichern
    11. byte* cb = dataptr; //aktueller Zeiger
    12. int cbsz = 1024; //Wähle Arraygröße, sodass die erwarteten Zeichen mit hoher Wahrscheinlichkeit in den Puffer passen (für Unicode gilt i.A., dass 2 Bytes pro Char benötigt werden)
    13. char[] charbuffer = new char[cbsz];
    14. while (cb < dataptrend)
    15. {
    16. var keysz = *(int*)cb; //Länge des Eintrags lesen...
    17. cb += 4; //... und Zeiger um dessen Länge inkrementieren
    18. int maxCharCount = encoding.GetMaxCharCount(keysz);
    19. //Puffer so erweitern, dass alle Zeichen des Schlüssels in ihn passen. Der Puffer wächst nur und stets zu einer Potenz von 2 (d.h. es gilt stets 2^k = charbuffer.Length)
    20. if (cbsz < maxCharCount)
    21. {
    22. //erweitern, bis cbsz maxCharCount übersteigt
    23. do
    24. {
    25. cbsz *= 2;
    26. } while (cbsz < maxCharCount);
    27. Array.Resize(ref charbuffer, cbsz);
    28. }
    29. //Item der übergebenen Liste hinzufügen (TODO: prüfen, ob string(sbyte*, int, int, System.Text.Encoding) schneller ist, vermutlich aber nicht)
    30. fixed (char* charbufferptr = charbuffer)
    31. items.Add(
    32. new KeyValuePair<string, int>(new string(charbuffer, 0, encoding.GetChars(cb, keysz, charbufferptr, cbsz)),
    33. *(int*)cb));
    34. cb += keysz + 4; //Schlüssel- und Datengröße aufaddieren
    35. }
    36. }
    37. }


    @Acr0most
    Du hast mehrere Probleme:
    - Dadurch, dass sData wohl nicht zwangsweise ein String ist, ist es als Typ ungeeignet. Integer erfüllt alle Anforderungen und seine Handhabung ist äußerst praktisch
    - Mehrfache String-Konkatenation ist extrem ineffizient. Während die Strings a, b, c, d in a + b + c + d zu einem Aufruf von String.Concat aufgelöst werden, wird bei einer Schleife stets eine neue Instanz erzeugt. Verwende stattdessen System.Text.StringBuilder, String.Join oder String.Concat. Linq hilft dir hierbei auch sehr. In diesem Fall ist der Weg über System.Text.Encoding wohl der beste.
    - bArray.GetValue(0) stellt ein Byte dar, die Größe der Längenangabe ist aber nicht bekannt, d.h. das funktioniert tatsächlich nur im Fall von 1 Byte. Außerdem sollte man tatsächlich auf bArray(0) zurückgreifen, statt auf den Umweg über GetValue. Das kann effizienter ausgeführt werden

    Viele Grüße
    ~blaze~
    Sry @TE das ich den Thread bissl für meinen Lernerfolg/Verständis missbrauche :O - werde es auch kurz und knapp halten^^

    @~blaze~
    Danke dir. Sprich ich kann - da wir mit Bytes arbeiten generell Integer verwenden?
    Da ich mich bei solchen etwas komplexeren Themen recht schwer tue und mein Wissen bisher nur über den "Standard-Stoff" ausgeweitet ist, wollte ich einmal fragen, ob es für solche Themen wie Stringbuilder/Join etc. und der Effizienz der Datentypen in spezifischen Fällen - Lektüre gibt und/oder es einen anderen Weg gibt sich damit vertraut zu machen.


    Bevor ich hier komplett abschweife - wenn jemand dazu etwas hat -> gerne auch per PN, damit der Thread nicht ausufert.

    Vielen Dank.

    Acr0most
    Wenn das Leben wirklich nur aus Nullen und Einsen besteht, dann laufen sicherlich genügen Nullen frei herum. :D
    Signature-Move 8o
    kein Problem mit privaten Konversationen zu Thema XY :thumbup:
    Kurz und knapp: Ich kenne keine Lektüre für sowas. Es ist einfach Erfahrung, die da aus mir spricht. I.a. lässt sich mit ausreichend Information und Hintergrundwissen das Verhalten einer Funktion halbwegs vorhersagen (bzw. die Dokumentation, sofern gut, liefert alle relevanten Infos). Performance lässt sich nur über den Daumen peilen, da das Problem häufig ist, dass man bei Funktionen auf eine "Blackbox" schaut, d.h. man kennt die konkrete Implementation nicht unbedingt. Es hilft aber, ein generelles Verständnis für die verwendeten Bauteile zu entwickeln.

    Was Bytes und Integer betrifft: int besteht aus 4 bytes, daher kann man sie quasi so verwenden, als seien sie ein 4-elementiges byte-Array (funktioniert auch mit 2 Short, z.B.). Rechnet man mit einem byte-Array oder arbeitet auf Bit-Basis, hat man häufig auf dem Array komplexere oder performancelastigere Algorithmen und auch die Speichereffizienz profitiert von so einer Darstellung in Bezug auf 4-Byte-Arrays. Nachteil ist, dass man an die Größe 4 byte fest gebunden ist.
    Was dann noch hinzukommt, sind Zeiger. Arrays und generell Typen mit entsprechenden Voraussetzungen (hab' grade keinen Link da, aber die exakten Voraussetzungen dürften sein, dass die Daten "primitiv darstellbar sind", also nur aus primitiven Daten, wie byte, int, Zeigern, usw. bestehen oder sequentielles Layout haben oder so, ich kenne die exakte Verallgemeinerung nicht) lassen sich anpinnen, sodass man auf die darunterliegenden Daten zugreifen kann. Das Anpinnen ist in .Net i.a. erforderlich, um ein Verschieben von Variablen durch die Garbage Collection zu verhindern. Auf jeden Fall lassen sich Zeiger halt einfach umcasten, sodass man z.B. aus einem byte* ein int* oder auch ein int** (Zeiger auf Zeiger) casten kann.
    In VB.Net gibt es dieses Feature afaik noch immer nicht.

    Viele Grüße
    ~blaze~