Ich habe Schwierigkeiten zu verstehen, was Serialisierung ist und tut.
Lass mich mein Problem vereinfachen. Ich habe eine struct info
in meinen c / c ++ Programmen, und ich kann diese struct
Daten in eine Datei save.bin
speichern oder sie über den Socket an einen anderen Computer senden.
write_to_file
speichert einfach das struct info
object a
auf dem Datenträger, was diese Daten persistent macht, oder? Und schreiben Sie es an die Steckdose ist ziemlich gleich, oder?
Im obigen Code glaube ich nicht, dass ich Datenserialisierung verwendet habe, aber die Daten a
werden trotzdem in save.bin
persistent gemacht, richtig?
Frage
Was ist der Sinn der Serialisierung? Brauche ich es hier? Wenn ja, wie soll ich es verwenden?
Ich denke immer, dass jede Art von Dateien, .txt/.csv/.exe/...
, Bits von 01
im Speicher sind, was bedeutet, dass sie natürlich Binärdarstellungen haben, also können wir diese Dateien nicht einfach über Socket senden?
Codebeispiel wird sehr geschätzt.
aber die Daten werden in save.bin trotzdem persistent gemacht, oder?
Nein! Ihre Struktur enthält std::string
. Die genaue Implementierung (und die binären Daten, die Sie mit einer Umwandlung in char*
erhalten, ist nicht vom Standard definiert, aber die tatsächlichen String-Daten werden immer irgendwo außerhalb des Klassenrahmens zurückgeliefert, dem Heap zugewiesen, so dass Sie das nicht speichern können Daten leicht gemacht. Bei richtiger Serialisierung werden die String-Daten dorthin geschrieben, wo der Rest der Klasse ebenfalls endet, so dass Sie sie aus einer Datei zurücklesen können. Dafür benötigen Sie die Serialisierung.
Wie es gemacht wird: Sie müssen den String irgendwie kodieren, der einfachste Weg ist, zuerst seine Länge und dann den String selbst zu schreiben. Lesen Sie beim Lesen der Datei zuerst die Länge zurück und lesen Sie dann die Anzahl der Bytes in ein neues String-Objekt.
Ich denke immer, dass jede Art von Dateien, .txt / .csv / .exe / ..., Bits von 01 im Speicher sind
Ja, aber das Problem ist, dass nicht allgemein definiert ist, welches Bit welchen Teil einer Datenstruktur repräsentiert. Insbesondere gibt es little-endian und big-endian Architekturen , sie speichern die Bits "andersherum". Wenn Sie naiv eine Datei lesen, die in einer nicht übereinstimmenden Architektur geschrieben ist, werden Sie offensichtlich Müll bekommen.
Das bloße Aufzeichnen binärer In-Memory-Images ist eine Form der Serialisierung und für triviale Fälle funktioniert es. Im Allgemeinen müssen Sie jedoch noch ein paar weitere Probleme lösen, die beim Speichern von Speicher nicht berücksichtigt werden:
Wenn die Daten irgendeinen Zeiger enthalten, können Sie eine Last nicht einfach später ausgeben, da die Speicheradresse, auf die die Zeiger zeigen, keine Bedeutung mehr hat, sobald das Programm beendet und neu gestartet wird. Viele Objekte haben "versteckte" Zeiger ... zum Beispiel gibt es keine Möglichkeit, ein std::vector
im Speicher abzulegen und später wieder korrekt zu laden ... sizeof
auf einem std::vector
schließt eindeutig die Größe der enthaltenen Elemente und nicht ein Daher kann jede Struktur, die ein std::vector
enthält, nicht einfach nur gedumpt und neu geladen werden. Dasselbe gilt für std::string
und alle anderen std
Container.
C und C ++ Struktur und Klassen sind nicht in Bezug auf die Bytes definiert, die sie im Speicher belegen, nicht portabel. Dies bedeutet, dass ein anderer Compiler, eine andere Compiler-Version oder sogar die gleiche Version, aber mit unterschiedlichen Kompilierungsoptionen Code generieren kann, in dem das Strukturlayout im Speicher nicht identisch ist.
Wenn Sie eine Serialisierung benötigen, um die Daten im selben Programm zu speichern und erneut zu laden, und die Daten, die sie nicht lange leben soll, dann kann Speicherdumping tatsächlich verwendet werden. Denken Sie jedoch daran, dass Millionen von Dokumenten nur durch das Dumping von Strukturen gespeichert werden und nun die neue Compiler-Version (die Sie forced verwenden müssen, weil sie die einzige unter der neuen OS-Version ist) hat ein anderes Layout und Sie können diese Dokumente nicht mehr laden.
Zusätzlich zu den Problemen mit der Portabilität des Systems sollten Sie beachten, dass auch nur eine einzelne Ganzzahl eine andere speicherinterne Darstellung auf verschiedenen Systemen haben kann. Es kann größer oder kleiner sein; Es kann eine andere Byte-Reihenfolge haben. Wenn Sie nur einen Speicherabzug verwenden, können die gespeicherten Daten nicht von einem anderen System geladen werden. Nicht einmal eine ganze Zahl.
Wenn die von Ihnen gespeicherten Daten eine lange Lebensdauer haben, ist es sehr wahrscheinlich, dass Sie die Strukturen bei der Entwicklung des Programms ändern. Sie werden beispielsweise neue Felder hinzufügen, nicht verwendete Felder entfernen und die allgemeine Struktur ändern ( zB Ändern eines Vektors in eine verknüpfte Liste).
Wenn Ihr Format nur die Speicherabbilder aktueller Datenstrukturen ist, wird es ziemlich schwer sein, später zB ein color
-Feld zu einem polygon
-Objekt hinzuzufügen und das Programm kann alte Dokumente laden, vorausgesetzt, dass Standardfarbwert ist die Farbe, die in der vorherigen Version verwendet wurde.
Selbst das Schreiben eines Konvertierungsprogramms wird schwierig, weil Sie alten Code haben, der alte Dokumente und neuen Code laden kann, um neue Dokumente zu speichern, aber Sie können nicht einfach beide zusammenführen und ein Programm bekommen, das alt lädt und neu speichert (dh der Quellcode beider Programme wird eine polygon
Struktur haben, aber mit anderen Feldern, was nun?).
Ihre Zeichenfolge wird nicht korrekt gespeichert. Wenn Sie unterschiedliche Maschinen haben, können sich ihre Darstellungen von Ganzzahlen unterscheiden, verschiedene Programmiersprachen haben zum Beispiel nicht die gleichen Darstellungen für Zeichenketten.
Aber wenn Sie Zeiger auf Mitglieder haben, speichern Sie die Zeigeradresse und nicht das auf das Mitglied gerichtete, was bedeutet, dass Sie keine Möglichkeit haben, diese Daten wieder aus der Datei zu bekommen. Was ist, wenn sich Ihre Struktur ändern muss? Alle Software, die Ihre Daten verwendet, muss sich ändern.
Ja, Sie können Dateien über den Socket senden, aber Sie werden eine Art von Protokoll benötigen, um sicherzustellen, dass Sie den Namen der Datei kennen und wenn Sie das Ende der Datei erreicht haben.
Serialisierung macht viele Dinge. Es unterstützt Persistenz (in der Lage sein um das Programm zu verlassen, dann zurückkommen und die gleichen Daten erhalten), und Kommunikation zwischen Prozessen und Maschinen. Es bedeutet im Grunde genommen Konvertieren Sie Ihre internen Daten in eine Sequenz von Bytes, und um nützlich zu sein, Sie müssen auch die Deserialisierung unterstützen: Konvertieren der Sequenz von Bytes zurück in Daten.
Wenn Sie dies tun, ist es wichtig, das intern zu erkennen
Programm, Daten sind nicht nur eine Folge von Bytes. Es hat Format und
Struktur: Wie ein double
dargestellt wird unterscheidet sich von einer Maschine
zum nächsten, zum Beispiel; und komplexere Objekte wie std::string
,
sind nicht einmal in zusammenhängender Erinnerung. Also das erste, was du tun musst
Wenn Sie serialisieren, definieren Sie, wie jeder Typ als Sequenz dargestellt wird
von Bytes. Wenn Sie mit einem anderen Programm kommunizieren, beide Programme
müssen sich auf dieses serielle Format einigen; wenn es nur so ist, dass du es nochmal lesen kannst
die Daten selbst, können Sie jedes Format verwenden, das Sie wollen (aber ich würde empfehlen
Verwendung eines vordefinierten Standardformats, wie XDR, wenn nur zur Vereinfachung der
Dokumentation).
Was Sie nicht tun können, ist ein Bild des Objekts im Speicher auszugeben.
Komplexe Objekte wie std::string
enthalten Zeiger und diese
Zeiger werden in einem anderen Prozess bedeutungslos sein. Und sogar die
Die Darstellung einfacher Typen wie double
kann sich im Laufe der Zeit ändern. (Das
Migration von 32 Bit auf 64 führte zu einer Änderung der Größe von long
auf den meisten Systemen.) Sie müssen ein Format definieren und dann ein Byte generieren
by byte, aus den Daten, die Sie haben. Zum Beispiel könnten Sie XDR schreiben
Verwenden Sie etwas wie folgt:
Du spielst ein Spiel. Im sehr harten Modus. Du erreichst das letzte Level. Du bist glücklich. Die 2 Tage Non-Stop-Spiel zahlen sich aus. Die Handlung wird bald zu Ende sein. Du wirst die Motivation des bösen Mastermind finden, wie du zum Helden geworden bist und das begehrte epische Artefakt sammeln, das hinter dieser letzten Tür wartet. Und Sie sind hier angekommen, ohne einmal neu gestartet zu werden.
Hinter den Kulissen gibt es ein Spielobjekt, das so aussieht:
%Vor% Und das Level ist 25
.
Sie haben das Spiel bisher sehr genossen, aber Sie wollen nicht von vorne anfangen, falls der letzte Boss Sie umbringt. Sie drücken intuitiv Ctrl+S
. Aber warten Sie, Sie erhalten einen Fehler:
Was? Also muss ich von vorn anfangen, falls ich sterbe? Wie kann das sein.
Trommelwirbel
Die Entwickler, wenn auch brilliant (sie haben es geschafft, Sie für zwei Tage in Folge zu halten, oder?), haben die Serialisierung nicht implementiert.
Wenn Sie das Spiel neu starten, findet eine Speicherbereinigung statt. Das alles wichtige GameState
-Objekt, das Sie 2 Tage verbracht haben, um level
member auf 25
zu erhöhen, wird zerstört.
Wie konntest du das reparieren? Der Speicher wird vom Betriebssystem wiederhergestellt, wenn Sie das Spiel schließen. Wo könnten Sie es speichern? Auf einem externen Server? (Sockets) Auf der Festplatte? (in Datei schreiben)
Okay, warum nicht.
%Vor% Wenn Sie Ctrl+s
drücken, wird das Objekt GameState
in einer Datei gespeichert.
Und wie durch ein Wunder wird beim Laden des Spiels das GameState
-Objekt aus dieser Datei gelesen. Sie müssen nicht mehr 2 Tage verbringen, um zu diesem letzten Boss zurückzukehren. Du bist schon da.
Echte Antwort:
Technisch gesehen ist das Schreiben von Serialisierungsfunktionen ziemlich schwierig. Ich schlage vor, Sie verwenden einen Dritten. Google-Protokollpuffer bieten eine plattformübergreifende und sogar sprachenübergreifende Serialisierung. Viele andere existieren.
1.Was ist der Sinn der Serialisierung? Brauche ich es hier? Wenn ja, wie soll ich es verwenden?
Wie oben erläutert, speichert es den Status zwischen Läufen oder zwischen Prozessen (möglicherweise auf verschiedenen Maschinen). Ob Sie benötigt oder nicht, hängt davon ab, ob Sie den Status speichern und später erneut laden müssen.
2. Ich denke immer, dass jede Art von Dateien, .txt / .csv / .exe / ..., Bits von 01 im Speicher sind, was bedeutet, dass sie natürlich binäre Darstellung haben, also können wir diese nicht einfach senden Dateien über Socket direkt?
Sie sind. Aber du möchtest das .exe
nicht ändern, wenn du ein neues Spiel spielst.
Abgesehen von big edian oder little endian gibt es die Frage, wie die Daten für die gegebene Struktur für das Programm mit diesem Compiler gepackt werden. Wenn Sie eine ganze Struktur speichern möchten, können Sie keine Zeiger verwenden. Sie müssten sie durch einen Zeichenpuffer ersetzen, der groß genug für Ihre Anforderungen ist. Wenn die andere Maschine die gleiche Architektur hat, dann werden bei Verwendung von #pragma pack (1) keine Lücken zwischen den Feldern Ihrer Struktur entstehen und Sie können sicherstellen, dass die Daten so aussehen, als ob sie serialisiert wären. aber ohne das Größenpräfix für Ihre Zeichenfolge. Sie können das Paket #pragma (1) überspringen, wenn Sie sicher sind, dass das andere Programm, das die Daten liest, genau die gleichen Einstellungen für dieselbe exakte Struktur hat. Ansonsten stimmen die Daten nicht überein.
Wenn Sie zuerst in den Speicher serialisieren, können Sie den Serialisierungsprozess beschleunigen. Dies kann normalerweise mit einer Pufferklasse und einer Template-Funktion für die meisten Typen erreicht werden.
%Vor%Offensichtlich werden Sie spezielle für Strings und größere Datentypen benötigen. Sie können memcpy für große Strukturen verwenden und Zeiger auf Daten übergeben. Für Strings sollten Sie die Länge wie zuvor erwähnt voranstellen.
Für seriöse Serialisierungsanforderungen gibt es jedoch noch viel mehr zu beachten.
Tags und Links c++ serialization