Ich bin Softwareentwickler und nach einer Diskussion mit einigen Kollegen habe ich festgestellt, dass ich die Serialisierung des Konzepts nicht gut verstehe. Wie ich verstehe, ist Serialisierung der Prozess des Konvertierens einer Entität, wie z. B. eines Objekts in OOP, in eine Folge von Bytes, so dass die Entität für den nachfolgenden Zugriff gespeichert oder übertragen werden kann (der Prozess der "Deserialisierung").
Das Problem ist, dass nicht alle Variablen (seien es primitive int
Objekte oder zusammengesetzte Objekte) bereits durch eine Folge von Bytes dargestellt werden. (Natürlich, weil sie in Registern, im Speicher, auf der Festplatte usw. gespeichert sind.)
Was macht die Serialisierung zu einem so tiefen Thema? Können wir zum Serialisieren einer Variablen diese Bytes nicht einfach in den Speicher nehmen und in eine Datei schreiben? Welche Feinheiten habe ich übersehen?
4 bytes
auf meinem PDP-11 serialisiere und dann versuche, dieselben vier Bytes in den Speicher meines Macbooks zu lesen, sind sie nicht dieselbe Nummer (wegen Endianes). Sie müssen also die Daten zu einer Darstellung normalisieren, die Sie entschlüsseln können (dies ist Serialisierung). Wie Sie die Daten serialisieren, hat auch Kompromisse Geschwindigkeit / Flexibilität Mensch / Maschine lesbar.Antworten:
Wenn Sie eine komplizierte Datenstruktur haben, ist die Darstellung im Speicher normalerweise über den gesamten Speicher verteilt. (Denken Sie zum Beispiel an einen binären Baum.)
Wenn Sie es dagegen auf die Festplatte schreiben möchten, möchten Sie wahrscheinlich eine Darstellung als (hoffentlich kurze) Folge zusammenhängender Bytes haben. Das ist es, was die Serialisierung für Sie bewirkt.
quelle
Betrachten Sie ein Objektdiagramm in C mit Knoten, die wie folgt definiert sind:
Zur Laufzeit würde der gesamte Objektgraph im gesamten
Node
Speicherbereich verstreut sein, und auf denselben Knoten könnte von vielen verschiedenen Knoten aus verwiesen werden.Sie können den Speicher nicht einfach in eine Datei / einen Stream / eine Festplatte sichern und als serialisiert bezeichnen, da die Zeigerwerte (die Speicheradressen sind) nicht de-serialisiert werden konnten (da diese Speicherorte möglicherweise bereits belegt sind, wenn Sie den Speicherauszug zurückladen in Erinnerung). Ein weiteres Problem beim einfachen Speicherauszug ist, dass Sie am Ende alle Arten irrelevanter Daten und nicht verwendeten Speicherplatzes speichern. Auf x86 verfügt ein Prozess über bis zu 4 GB Speicherplatz, und ein Betriebssystem oder eine MMU hat nur eine allgemeine Vorstellung davon, was Speicher tatsächlich ist Sinnvoll oder nicht (basierend auf den Speicherseiten, die einem Prozess zugewiesen wurden).
Notepad.exe
Daher erscheint es etwas verschwenderisch , 4 GB unformatierte Bytes auf meiner Festplatte abzulegen, wenn ich eine Textdatei speichern möchte.Ein weiteres Problem ist die Versionierung: Was passiert, wenn Sie Ihr
Node
Diagramm an Tag 1 serialisieren , dann an Tag 2 ein weiteres Feld hinzufügenNode
(z. B. einen anderen Zeigerwert oder einen primitiven Wert) und dann an Tag 3 Ihre Datei de-serialisieren Tag 1?Sie müssen auch andere Dinge berücksichtigen, wie Endianness. Einer der Hauptgründe, warum MacOS- und IBM / Windows / PC-Dateien in den 1980er und 1990er Jahren inkompatibel waren, obwohl sie angeblich mit denselben Programmen (Word, Photoshop usw.) erstellt wurden, waren die Multi-Byte-Ganzzahlwerte für x86 / PC wurden in Little-Endian-Reihenfolge, aber in Big-Endian-Reihenfolge auf dem Mac gespeichert - und die Software wurde nicht mit Blick auf plattformübergreifende Portabilität erstellt. Heutzutage sieht es dank einer verbesserten Entwicklerausbildung und unserer zunehmend heterogenen Computerwelt besser aus.
quelle
Die knifflig eigentlich schon im Wort beschrieben selbst: „ serielle isierung“.
Die Frage ist im Grunde: Wie kann ich einen beliebig komplexen zusammenhängenden zyklisch gerichteten Graphen beliebig komplexer Objekte als lineare Folge von Bytes darstellen?
Denken Sie darüber nach: Eine lineare Sequenz ähnelt einem entarteten gerichteten Graphen, bei dem jeder Scheitelpunkt genau eine ankommende und abgehende Kante hat (mit Ausnahme des "ersten Scheitelpunkts", der keine ankommende Kante hat, und des "letzten Scheitelpunkts", der keine abgehende Kante hat). . Und ein Byte ist offensichtlich weniger komplex als ein Objekt .
So scheint es sinnvoll, dass wir von einem beliebig komplexen Graphen zu einem viel mehr eingeschränkt „Graph“ gehen (eigentlich nur eine Liste) und von beliebig komplexe Objekte auf einfache Bytes, Informationen werden verloren gehen, wenn wir dies naiv tun und don‘ t Codieren Sie die "fremden" Informationen in irgendeiner Weise. Und genau das leistet die Serialisierung: Sie codiert die komplexen Informationen in ein einfaches lineares Format.
Wenn Sie mit YAML vertraut sind , sehen Sie sich möglicherweise die Anker- und Alias- Funktionen an, mit denen Sie die Vorstellung vertreten können, dass "dasselbe Objekt an verschiedenen Stellen auftreten kann".
ZB wenn Sie folgendes Diagramm haben:
Sie können dies in YAML als Liste linearer Pfade wie folgt darstellen:
Sie können es auch als Adjazenzliste oder Adjazenzmatrix oder als Paar darstellen, dessen erstes Element eine Menge von Knoten und dessen zweites Element eine Menge von Knotenpaaren ist. In all diesen Darstellungen müssen Sie jedoch über Folgendes verfügen Eine Möglichkeit, auf vorhandene Knoten, dh auf Zeiger , die in einer Datei oder einem Netzwerk-Stream normalerweise nicht vorhanden sind, vorwärts und rückwärts zu verweisen . Am Ende haben Sie nur noch Bytes.
(Was übrigens bedeutet, dass die oben genannte YAML-Textdatei selbst auch "serialisiert" werden muss, wofür die verschiedenen Zeichenkodierungen und Unicode-Übertragungsformate vorgesehen sind. Es handelt sich nicht ausschließlich um "Serialisierung", sondern nur um Kodierung, da die Textdatei bereits eine serielle Datei ist / lineare Liste von Codepunkten, aber Sie können einige Ähnlichkeiten sehen.)
quelle
Die anderen Antworten befassen sich bereits mit komplexen Objektgraphen, es sei jedoch darauf hingewiesen, dass die Serialisierung von Primitiven ebenfalls nicht trivial ist.
Berücksichtigen Sie bei der Verwendung von C-Primitivtypnamen Folgendes:
Ich serialisiere a
long
. Einige Zeit später de-serialisiere ich es, aber ... auf einer anderen Plattform, und jetztlong
ist esint64_t
eher als das, wasint32_t
ich gespeichert habe. Daher muss ich entweder sehr vorsichtig mit der genauen Größe jedes von mir gespeicherten Typs sein oder einige Metadaten speichern, die den Typ und die Größe jedes Felds beschreiben.Beachten Sie, dass diese andere Plattform nach einer zukünftigen Neukompilierung möglicherweise dieselbe Plattform ist.
Ich serialisiere eine
int32_t
. Einige Zeit später habe ich es de-serialisiert, aber ... auf einer anderen Plattform, und jetzt ist der Wert beschädigt. Leider habe ich den Wert auf einer Big-Endian-Plattform gespeichert und auf eine Little-Endian-Plattform geladen. Jetzt muss ich eine Konvention für mein Format erstellen oder weitere Metadaten hinzufügen , die die Endianität jeder Datei / jedes Streams / was auch immer beschreiben. Und natürlich tatsächlich die entsprechenden Konvertierungen durchführen.char
UTF-8 und einewchar_t
UTF-16.Daher würde ich behaupten, dass Serialisierung in angemessener Qualität auch für Grundelemente im zusammenhängenden Speicher nicht trivial ist. Es gibt viele Kodierungsentscheidungen, die Sie entweder dokumentieren oder mit Inline-Metadaten beschreiben müssen.
Objektgraphen erhöhen die Komplexität zusätzlich.
quelle
Es gibt mehrere Aspekte:
Lesbarkeit mit dem gleichen Programm
Ihr Programm hat Ihre Daten irgendwie als Bytes im Speicher abgelegt. Aber es kann willkürlich über verschiedene Register verteilt sein, wobei Zeiger zwischen ihren kleineren Teilen hin und her gehen. . Denken Sie nur an eine verknüpfte Ganzzahlliste. Jedes Listenelement kann an einem völlig anderen Ort gespeichert werden, und alles, was die Liste zusammenhält, sind die Zeiger von einem Element zum nächsten. Wenn Sie diese Daten unverändert übernehmen und versuchen, sie auf einen anderen Computer zu kopieren, auf dem dasselbe Programm ausgeführt wird, treten Probleme auf:
Lesbarkeit durch ein anderes Programm
Angenommen, Sie können genau die richtigen Adressen auf einem anderen Computer zuweisen, damit Ihre Daten darin passen. Wenn Ihre Daten von einem separaten Programm auf diesem Computer verarbeitet werden (in einer anderen Sprache), hat dieses Programm möglicherweise ein völlig anderes Grundverständnis für Daten. Angenommen, Sie haben C ++ - Objekte mit Zeigern, aber Ihre Zielsprache unterstützt auf dieser Ebene nicht einmal Zeiger. Wiederum haben Sie keine saubere Möglichkeit, diese Daten im zweiten Programm zu adressieren. Am Ende befinden sich einige Binärdaten im Speicher, aber dann müssen Sie zusätzlichen Code schreiben, der die Daten umschließt und sie irgendwie in etwas übersetzt, mit dem Ihre Zielsprache arbeiten kann. Klingt nach Deserialisierung, nur dass Ihr Ausgangspunkt jetzt ein seltsames Objekt ist, das in Ihrem Hauptspeicher verstreut ist und für verschiedene Ausgangssprachen unterschiedlich ist. anstelle einer Datei mit einer klar definierten Struktur. Das Gleiche gilt natürlich, wenn Sie versuchen, die Binärdatei mit den Zeigern direkt zu interpretieren. Sie müssen Parser für jede mögliche Art und Weise schreiben, in der eine andere Sprache möglicherweise Daten im Speicher darstellt.
Lesbarkeit durch einen Menschen
Zwei der bekanntesten modernen Serialisierungssprachen für die webbasierte Serialisierung (xml, json) sind für den Menschen leicht verständlich. Anstelle eines binären Haufens von Daten ist die tatsächliche Struktur und der Inhalt der Daten auch ohne ein Programm zum Lesen der Daten klar. Dies hat mehrere Vorteile:
quelle
Zusätzlich zu dem, was die anderen Antworten gesagt haben:
Manchmal möchten Sie Dinge serialisieren, die keine reinen Daten sind.
Denken Sie beispielsweise an ein Datei-Handle oder eine Verbindung zu einem Server. Obwohl das Dateihandle oder der Socket ein ist
int
, ist diese Nummer beim nächsten Ausführen des Programms bedeutungslos. Um Objekte, die Handles für solche Dinge enthalten, ordnungsgemäß neu zu erstellen, müssen Sie Dateien erneut öffnen und Verbindungen neu erstellen und entscheiden, was zu tun ist, wenn dies fehlschlägt.Heutzutage unterstützen viele Sprachen das Speichern anonymer Funktionen in Objekten, beispielsweise ein
onBlah()
Handler in Javascript. Dies ist eine Herausforderung, da dieser Code Verweise auf zusätzliche Datenelemente enthalten kann, die wiederum serialisiert werden müssen. (Und dann gibt es das Problem der plattformübergreifenden Serialisierung von Code, das für interpretierte Sprachen offensichtlich einfacher ist.) Auch wenn nur eine Teilmenge der Sprache unterstützt werden kann, kann es sich dennoch als recht nützlich erweisen. Nicht viele Serialisierungsmechanismen versuchen, Code zu serialisieren, sehen aber serialize-javascript .In Fällen, in denen Sie ein Objekt serialisieren möchten, das jedoch etwas enthält, das von Ihrem Serialisierungsmechanismus nicht unterstützt wird, müssen Sie den Code so umschreiben, dass dies umgeht. Beispielsweise können Sie anstelle anonymer Funktionen Aufzählungen verwenden, wenn eine begrenzte Anzahl von Funktionen möglich ist.
Oft möchten Sie, dass die serialisierten Daten kurz sind.
Wenn Sie Daten über das Netzwerk senden oder sogar auf einer Festplatte speichern, kann es wichtig sein, die Größe klein zu halten. Eine der einfachsten Möglichkeiten, dies zu erreichen, besteht darin, Informationen zu verwerfen, die wiederhergestellt werden können (z. B. das Löschen von Caches, Hash-Tabellen und alternativen Darstellungen derselben Daten).
Natürlich muss der Programmierer manuell auswählen, was gespeichert und was verworfen werden soll, und sicherstellen, dass die Dinge neu erstellt werden, wenn das Objekt neu erstellt wird.
Denken Sie über das Speichern eines Spiels nach. Objekte können viele Zeiger auf Grafikdaten, Sounddaten und andere Objekte enthalten. Das meiste davon kann jedoch aus den Spieledatendateien geladen werden und muss nicht in einer Sicherungsdatei gespeichert werden. Es kann mühsam sein, es zu verwerfen, so dass oftmals kleine Dinge übrig bleiben. Ich habe einige Sicherungsdateien in meiner Zeit verhext und Daten entdeckt, die eindeutig redundant waren, wie z. B. textuelle Artikelbeschreibungen.
Manchmal ist der Speicherplatz nicht wichtig, aber die Lesbarkeit. In diesem Fall können Sie stattdessen ein ASCII-Format (möglicherweise JSON oder XML) verwenden.
quelle
Definieren wir, was eine Folge von Bytes ist. Eine Folge von Bytes besteht aus einer nicht negativen Ganzzahl, die als Länge bezeichnet wird, und einer beliebigen Funktion / Entsprechung, die eine beliebige Ganzzahl i , die mindestens null und kürzer als die Länge ist , einem Bytewert (einer Ganzzahl von 0 bis 255) zuordnet.
Viele der Objekte, mit denen Sie sich in einem typischen Programm befassen, haben nicht diese Form, da die Objekte tatsächlich aus vielen verschiedenen Speicherzuordnungen bestehen, die sich an verschiedenen Stellen im RAM befinden und durch Millionen von Bytes voneinander getrennt sein können ist mir egal. Stellen Sie sich eine einfache verknüpfte Liste vor: Jeder Knoten in der Liste besteht aus einer Folge von Bytes, aber die Knoten befinden sich an vielen verschiedenen Stellen im Arbeitsspeicher Ihres Computers und sind mit Zeigern verbunden. Oder stellen Sie sich eine einfache Struktur vor, die einen Zeiger auf eine Zeichenfolge variabler Länge enthält.
Der Grund, warum wir Datenstrukturen in eine Folge von Bytes serialisieren möchten, liegt normalerweise darin, dass wir sie auf der Festplatte speichern oder an ein anderes System senden möchten (z. B. über das Netzwerk). Wenn Sie versuchen, einen Zeiger auf der Festplatte zu speichern oder an ein anderes System zu senden, ist dies ziemlich unbrauchbar, da das Programm, das diesen Zeiger liest, über einen anderen Satz von verfügbaren Speicherbereichen verfügt.
quelle
int seq(int i) { if (0 <= i < length) return i+1; else return -1;}
ist eine Sequenz. Wie kann ich das auf der Festplatte speichern?sin
in eine Nachschlagetabelle zu konvertieren , die eine Folge von Zahlen ist? Wussten Sie, dass Ihre Funktion für die Eingänge, die uns wichtig sind, dieselbe ist wie diese?int seq(n) { int a[] = [1, 2, 3, 4]; return a[n]; }
Warum genau sagen Sie, dass meine 4-Byte-Datei eine unzureichende Darstellung ist?Die Feinheiten spiegeln die Feinheiten von Daten und Objekten selbst wider. Diese Objekte können Objekte der realen Welt oder Computerobjekte sein. Die Antwort liegt im Namen. Serialisierung ist die lineare Darstellung mehrdimensionaler Objekte. Es gibt viele andere Probleme als fragmentierten RAM.
Wenn Sie 12 fünfdimensionale Arrays und Programmcode reduzieren können, können Sie durch Serialisierung auch ein gesamtes Computerprogramm (und Daten) zwischen Computern übertragen. Verteilte Computerprotokolle wie RMI / CORBA verwenden die Serialisierung in großem Umfang, um Daten und Programme zu übertragen.
Betrachten Sie Ihre Telefonrechnung. Es kann sich um ein einzelnes Objekt handeln, das aus all Ihren Anrufen (Liste der Zeichenfolgen), dem zu zahlenden Betrag (Ganzzahl) und dem Land besteht. Oder Ihre Telefonrechnung kann von oben nach unten verwechselt werden und aus diskreten Einzelanrufen bestehen, die mit Ihrem Namen verknüpft sind. Jede Abflachung sieht anders aus und spiegelt wider, wie Ihre Telefongesellschaft diese Version der Software geschrieben hat und warum objektorientierte Datenbanken nie veröffentlicht wurden.
Einige Teile einer Struktur befinden sich möglicherweise überhaupt nicht im Speicher. Wenn Sie einen langsamen Cache haben, werden einige Teile eines Objekts möglicherweise nur auf eine Festplattendatei referenziert und nur geladen, wenn auf diesen Teil des bestimmten Objekts zugegriffen wird. Dies ist häufig bei schwerwiegenden Persistenz-Frameworks der Fall. BLOBs sind ein gutes Beispiel. Getty Images speichert möglicherweise ein riesiges Multi-Megabyte-Bild von Fidel Castro sowie einige Metadaten wie den Namen des Bildes, die Mietkosten und das Bild selbst. Vielleicht möchten Sie das 200-MB-Bild nicht jedes Mal in den Speicher laden, es sei denn, Sie sehen ihn tatsächlich an. Serialisiert würde die gesamte Datei über 200 MB Speicherplatz benötigen.
Einige Objekte können überhaupt nicht serialisiert werden. Im Land der Java-Programmierung können Sie ein Programmierobjekt haben, das den Grafikbildschirm oder eine physikalische serielle Schnittstelle darstellt. Es gibt kein wirkliches Konzept für eine Serialisierung. Wie würden Sie Ihren Port über ein Netzwerk an eine andere Person senden?
Einige Dinge wie Passwörter / Verschlüsselungsschlüssel sollten nicht gespeichert oder übertragen werden. Sie können als solche gekennzeichnet werden (flüchtig / vorübergehend usw.), und der Serialisierungsprozess überspringt sie, aber sie können im RAM leben. Wenn Sie diese Tags weglassen, werden die Verschlüsselungsschlüssel versehentlich in reinem ASCII gesendet / gespeichert.
Dies und die anderen Antworten sind der Grund, warum es kompliziert ist.
quelle
Ja, sind Sie. Das Problem hierbei ist das Layout dieser Bytes. Ein einfaches
int
kann 2, 4 oder 8 Bits lang sein. Es kann im großen oder kleinen Endian sein. Es kann unsigniert, mit 1er-Komplement signiert oder sogar mit einer super-exotischen Bitcodierung wie Negabinary versehen werden.Wenn Sie die
int
Binärdatei nur aus dem Speicher sichern und als "serialisiert" bezeichnen, müssen Sie so ziemlich den gesamten Computer, das Betriebssystem und Ihr Programm anschließen, damit sie deserialisierbar sind. Oder zumindest eine genaue Beschreibung.Die Serialisierung eines einfachen Objekts besteht darin, es nach bestimmten Regeln aufzuschreiben. Diese Regeln sind zahlreich und nicht immer offensichtlich. ZB wird ein
xs:integer
in XML in Base-10 geschrieben. Nicht Basis-16, nicht Basis-9, sondern 10. Es ist keine versteckte Annahme, es ist eine tatsächliche Regel. Und solche Regeln machen Serialisierung zu einer Serialisierung. Weil es so ziemlich keine Regeln für das Bit-Layout Ihres Programms im Speicher gibt .Das war nur eine Spitze eines Eisbergs. Nehmen wir ein Beispiel aus einer Folge von jenen einfachsten Primitiven: C
struct
. Das könnte man denkenhat ein definiertes Speicherlayout auf einem bestimmten Computer + Betriebssystem? Nun, das tut es nicht. Abhängig von der aktuellen
#pragma pack
Einstellung füllt der Compiler die Felder auf. In den Standardeinstellungen der 32-Bit-Kompilierung werden beideshorts
auf 4 Bytes aufgefüllt, sodassstruct
sich tatsächlich 3 Felder mit 4 Bytes im Speicher befinden. Sie müssen also nicht nur eineshort
Länge von 16 Bit angeben , sondern auch eine Ganzzahl, die im negativen 1er-Komplement, im großen oder im kleinen Endian geschrieben ist. Sie müssen auch die Strukturpackungseinstellung notieren, mit der Ihr Programm kompiliert wurde.Das ist so ziemlich das, worum es bei der Serialisierung geht: Regeln aufstellen und sie einhalten.
Diese Regeln können dann erweitert werden, um noch komplexere Strukturen (wie Listen variabler Länge oder nichtlineare Daten), zusätzliche Funktionen wie Lesbarkeit, Versionierung, Abwärtskompatibilität und Fehlerkorrektur usw. zu akzeptieren. Aber selbst das Aufschreiben einer einzelnen
int
ist für Sie schon kompliziert genug Ich möchte nur sicherstellen, dass Sie es zuverlässig zurücklesen können.quelle