Serialisierung und Deserialisierung in Java [Java-Interna Teil 6] [Zweiter Teil]
In diesem Artikel werden wir sehen, wie die Serialisierung in Java mithilfe der ObjectOutputStream-Klasse erfolgt. Wir werden uns zuerst die internen Implementierungen der Dinge ansehen, die wir in unseren ObjectOutputStream-Klassen verwenden, bevor wir zu ihrer Implementierung kommen, damit wir eine Geschichte des Verständnisses haben!
Wir diskutieren hier über die unten genannten Klassen:
- ObjectOutput-Schnittstelle
- BlockDataOutputStream
- HandleTable
- Tabelle ersetzen
erweitert OutputStream implementiert ObjectOutput und ObjectStreamConstants
- schreibt primitive Datentypen und Diagramme von Java-Objekten in den Stream
- Die Klasse jedes serialisierbaren Objekts wird mit codiert
2. Unterschrift der Klasse
3. Werte der Objektfelder und Arrays
4. Schließung aller anderen Objekte
- Die Methode writeObject wird zum Schreiben in einen Stream verwendet
- primitive Datentypen -> mit den Dataoutput-Methoden in den Stream geschrieben
- string kann mit der writeUTF-Methode geschrieben werden
- Die serialisierte Form einer Enum- Konstante besteht nur aus ihrem Namen
- Feldwerte der Konstanten werden nicht übertragen
- Der Prozess , durch den Enum-Konstanten serialisiert werden, kann nicht angepasst werden
- Alle klassenspezifischen writeObject- und writeReplace-Methoden werden ignoriert
- Sämtliche Felddeklarationen vom Typ „serialPersistantFields“ oder „SerialVersionUID“ werden ebenfalls ignoriert
- Aufzählungstyp hat serialVersionUUID => OL behoben
erweitert DataOutput , AutoCloseable .
DataOutput-Schnittstelle :
Unter dieser Schnittstelle gibt es mehrere Methoden:
In unserer ObjectOutput-Schnittstelle haben wir:
- void writeObject(Object obj)
- void write(int b) => schreibt ein Byte, diese Methode blockiert, bis ein Byte geschrieben ist
- void write(byte b[]) -> schreibt ein Array von Bytes und blockiert, bis es geschrieben wird
- void write(byte b[] , int off , int len) -> schreibt ein Subarray von Bytes
- void flush() -> leert den Stream und schreibt alle gepufferten Ausgabebytes
- void close() -> schließt den Stream
Mitgliedsvariablen:
BlockDataOutputStream:
Konstrukteur :
BlockDataOutputStream(OutputStream out)
{
this.out = out;
dout = new DataOutputStream(this);
}
- Legt den Datenmodus fest
- Wenn der aktuelle Modus mit der Eingabe übereinstimmt, wird keine Aktion ausgeführt
- andernfalls entleeren wir zuerst die gepufferten Daten
- Wir geben den vorherigen Moduswert zurück
schreibt alle gepufferten Daten aus diesem Stream in den zugrunde liegenden Stream.
void drain() throws IOException {
if (pos == 0) {
return;
}
if (blkmode) {
writeBlockHeader(pos);
}
out.write(buf, 0, pos);
pos = 0;
}
Writes block data header. Data blocks shorter than 256 bytes are prefixed
with a 2-byte header; all others start with a 5-byte header.
private void writeBlockHeader(int len) throws IOException {
if (len <= 0xFF) {
hbuf[0] = TC_BLOCKDATA;
hbuf[1] = (byte) len;
out.write(hbuf, 0, 2);
} else {
hbuf[0] = TC_BLOCKDATALONG;
Bits.putInt(hbuf, 1, len);
out.write(hbuf, 0, 5);
}
}
boolean setBlockDataMode(boolean mode) throws IOException {
if (blkmode == mode) {
return blkmode;
}
drain();
blkmode = mode;
return !blkmode;
}
void write(int b) :
Es prüft, ob die Position größer als die maximale Blockgröße ist -> drain()
Andernfalls werden die Daten in den Puffer geschrieben.
public void write(int b) throws IOException {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
buf[pos++] = (byte) b;
}
public void flush() throws IOException {
drain();
out.flush();
}
public void close() throws IOException {
flush();
out.close();
}
void write(byte[] b, int off, int len, boolean copy)
throws IOException
{
if we are not having blkmode or no need to copy then we can write
the bytes directly
if (!(copy || blkmode)) { // write directly
drain();
out.write(b, off, len);
return;
}
we simply keep writting untill our len > 0 , in case our pos goes out
we drain else we increment the offset and decrement the len as we go on
writting
while (len > 0) {
if (pos >= MAX_BLOCK_SIZE) {
drain();
}
if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) {
// avoid unnecessary copy
writeBlockHeader(MAX_BLOCK_SIZE);
out.write(b, off, MAX_BLOCK_SIZE);
off += MAX_BLOCK_SIZE;
len -= MAX_BLOCK_SIZE;
} else {
int wlen = Math.min(len, MAX_BLOCK_SIZE - pos);
System.arraycopy(b, off, buf, pos, wlen);
pos += wlen;
off += wlen;
len -= wlen;
}
}
}
HandleTable:
Es ist eine sehr interessante ähnliche Implementierung mit der hashTable .
Leichte Identitäts-Hash-Tabelle, die Objekte ganzzahligen Handles zuordnet, die in aufsteigender Reihenfolge zugewiesen werden.
Konstrukteur :
HandleTable(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
spine = new int[initialCapacity];
next = new int[initialCapacity];
objs = new Object[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
clear();
}
Weist dem angegebenen Objekt das nächste verfügbare Handle zu und gibt den Handle-Wert zurück. Handles werden in aufsteigender Reihenfolge beginnend bei 0 zugewiesen.
- Hier prüfen wir zunächst, ob unsere Größe die Kapazität unseres Arrays next.length überschritten hat [das nächste Array speichert den nächsten Handle-Wert des Kandidaten]
- im Fall size ≥ next.length erhöhen wir unsere Kapazität der Hash-Tabelle durch Verlängern der Eintragsarrays.
und wir kopieren unser nächstes Array in ein Array neuer Größe
Außerdem erstellen wir ein neues Array für Objekte
private void growEntries() {
int newLength = (next.length << 1) + 1;
int[] newNext = new int[newLength];
System.arraycopy(next, 0, newNext, 0, size);
next = newNext;
Object[] newObjs = new Object[newLength];
System.arraycopy(objs, 0, newObjs, 0, size);
objs = newObjs;
}
Daher hat unser neues Spine-Array die Größe prevSpineSize * 2 + 1
Wir haben jetzt einen neuen Schwellenwert, nämlich spine.length * LoadFactor
Anschließend rufen wir hier die Methode insert auf, um die Objekte wieder in der Hashtabelle zu speichern.
private void growSpine() {
spine = new int[(spine.length << 1) + 1];
threshold = (int) (spine.length * loadFactor);
Arrays.fill(spine, -1);
for (int i = 0; i < size; i++) {
insert(objs[i], i);
}
}
Wir finden den Hash-Index einfach, indem wir den Hash-Wert des Objekt-%-Spines ermitteln. Länge .
Dann fügen wir das Objekt zu unseren Objekten hinzu.
Wir fügen den nächsten Kandidaten zum Handle mit dem Wert am Index im Spine hinzu und aktualisieren schließlich den spine[index]-Wert .
private void insert(Object obj, int handle) {
int index = hash(obj) % spine.length;
objs[handle] = obj;
next[handle] = spine[index];
spine[index] = handle;
}
int assign(Object obj) {
if (size >= next.length) {
growEntries();
}
if (size >= threshold) {
growSpine();
}
insert(obj, size);
return size++;
}
Es hilft uns, das mit dem Objekt verknüpfte Handle anzugeben
Wir iterieren grundsätzlich vom Spine-[Index]-Wert und gehen weiter zum nächsten Wert, bis unsere Objekte übereinstimmen.
int lookup(Object obj) {
if (size == 0) {
return -1;
}
int index = hash(obj) % spine.length;
for (int i = spine[index]; i >= 0; i = next[i]) {
if (objs[i] == obj) {
return i;
}
}
return -1;
}
Die Ersetzungstabelle wird verwendet, um dem Ersatzobjekt im Stream zuzuordnen.
Es verwendet intern die Handle-Tabelle , die wir zuvor gesehen haben
und unterhält ein Ersatzobjekt.
Mitglieder:
Konstrukteur :
ReplaceTable(int initialCapacity, float loadFactor) {
htab = new HandleTable(initialCapacity, loadFactor);
reps = new Object[initialCapacity];
}
übernimmt den Index von htab, indem es das Objekt dort zuweist,
Prüft, ob wir den Wert am angegebenen Index hinzufügen können, falls wir die Tabelle vergrößern müssen. Dann rufen wir die Wachstumsfunktion auf und fügen schließlich den Wert am Ersatzindex ein.
void assign(Object obj, Object rep) {
int index = htab.assign(obj);
while (index >= reps.length) {
grow();
}
reps[index] = rep;
}
private void grow() {
Object[] newReps = new Object[(reps.length << 1) + 1];
System.arraycopy(reps, 0, newReps, 0, reps.length);
reps = newReps;
}
Object lookup(Object obj) {
int index = htab.lookup(obj);
return (index >= 0) ? reps[index] : obj;
}
- SerialCallBackContext
- DebugTraceInfoStack
Danke fürs Lesen !
Unterstützen Sie unsere Publikation, indem Sie ihr folgen
Effiziente Identifizierung von Primzahlen (1 — n) in JavaScript: Eine Schritt-für-Schritt-Anleitung – Tauchen Sie ein in die… Einführung in die Spark-Architektur . Erfahren Sie mehr über Begleitobjekte in Kotlin . Die Kunst der Gewohnheiten meistern: Eine umfassende Anleitung zum Aufbau besserer Gewohnheiten und zum Durchbrechen Schlechte Teil 1: Top(1-5) häufige JavaScript-Fragen Warum Java die beliebteste Programmiersprache auf dem Planeten ist Splunk-Integration mit SpringBoot Teil – 1 Das Clickjacking und seine vorbeugenden Maßnahmen Splunk-Integration mit SpringBoot – Teil 2 2023 React JS Trends schauen
![Was ist überhaupt eine verknüpfte Liste? [Teil 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































