Kryo ist ein schnelles und effizientes Serialisierungsframework für binäre Objektgraphen für Java. Die Ziele des Projekts sind hohe Geschwindigkeit, geringe Größe und eine einfach zu verwendende API. Das Projekt ist immer dann nützlich, wenn Objekte dauerhaft gespeichert werden müssen, sei es in einer Datei, einer Datenbank oder über das Netzwerk.
Kryo kann auch automatisches tiefes und flaches Kopieren/Klonen durchführen. Dies ist ein direktes Kopieren von Objekt zu Objekt, nicht von Objekt zu Byte zu Objekt.
Diese Dokumentation gilt für Kryo Version 5.x. Siehe Wiki für Version 4.x.
Bitte nutzen Sie die Kryo-Mailingliste für Fragen, Diskussionen und Support. Bitte beschränken Sie die Nutzung des Kryo-Issue-Trackers auf Fehler und Verbesserungen, nicht auf Fragen, Diskussionen oder Support.
Kryo veröffentlicht zwei Arten von Artefakten/Gläsern:
Kryo-JARs sind auf der Release-Seite und bei Maven Central verfügbar. Die neuesten Snapshots von Kryo, einschließlich Snapshot-Builds des Masters, befinden sich im Sonatype Repository.
Um die neueste Kryo-Version in Ihrer Anwendung zu verwenden, verwenden Sie diesen Abhängigkeitseintrag in Ihrer pom.xml
:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
Um die neueste Kryo-Version in einer Bibliothek zu verwenden, die Sie veröffentlichen möchten, verwenden Sie diesen Abhängigkeitseintrag in Ihrer pom.xml
:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
Um den neuesten Kryo-Schnappschuss zu verwenden, verwenden Sie:
< repository >
< id >sonatype-snapshots</ id >
< name >sonatype snapshots repo</ name >
< url >https://oss.sonatype.org/content/repositories/snapshots</ url >
</ repository >
<!-- for usage in an application: -->
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
<!-- for usage in a library that should be published: -->
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.3-SNAPSHOT</ version >
</ dependency >
Nicht jeder ist ein Maven-Fan. Um Kryo ohne Maven zu verwenden, müssen Sie die Kryo-JAR zusammen mit den in lib enthaltenen Abhängigkeits-JARs in Ihrem Klassenpfad platzieren.
Für die Erstellung von Kryo aus dem Quellcode sind JDK11+ und Maven erforderlich. Führen Sie Folgendes aus, um alle Artefakte zu erstellen:
mvn clean && mvn install
Um zu zeigen, wie die Bibliothek genutzt werden kann:
import com . esotericsoftware . kryo . Kryo ;
import com . esotericsoftware . kryo . io . Input ;
import com . esotericsoftware . kryo . io . Output ;
import java . io .*;
public class HelloKryo {
static public void main ( String [] args ) throws Exception {
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
SomeClass object = new SomeClass ();
object . value = "Hello Kryo!" ;
Output output = new Output ( new FileOutputStream ( "file.bin" ));
kryo . writeObject ( output , object );
output . close ();
Input input = new Input ( new FileInputStream ( "file.bin" ));
SomeClass object2 = kryo . readObject ( input , SomeClass . class );
input . close ();
}
static public class SomeClass {
String value ;
}
}
Die Kryo-Klasse führt die Serialisierung automatisch durch. Die Output- und Input-Klassen kümmern sich um das Puffern von Bytes und optional um das Leeren in einen Stream.
Der Rest dieses Dokuments beschreibt die Funktionsweise und die erweiterte Nutzung der Bibliothek.
Das Ein- und Auslesen von Daten in Kryo erfolgt mithilfe der Input- und Output-Klassen. Diese Klassen sind nicht threadsicher.
Die Output-Klasse ist ein OutputStream, der Daten in einen Byte-Array-Puffer schreibt. Dieser Puffer kann direkt bezogen und verwendet werden, wenn ein Byte-Array gewünscht wird. Wenn der Output einen OutputStream erhält, werden die Bytes in den Stream geleert, wenn der Puffer voll ist. Andernfalls kann der Output seinen Puffer automatisch vergrößern. Die Ausgabe verfügt über viele Methoden zum effizienten Schreiben von Grundelementen und Zeichenfolgen in Bytes. Es bietet ähnliche Funktionen wie DataOutputStream, BufferedOutputStream, FilterOutputStream und ByteArrayOutputStream, alle in einer Klasse.
Tipp: Ausgabe und Eingabe bieten alle Funktionen von ByteArrayOutputStream. Es gibt selten einen Grund, Output in einen ByteArrayOutputStream zu leeren.
Output puffert die Bytes beim Schreiben in einen OutputStream. Daher muss flush
oder close
aufgerufen werden, nachdem der Schreibvorgang abgeschlossen ist, damit die gepufferten Bytes in den OutputStream geschrieben werden. Wenn für die Ausgabe kein OutputStream bereitgestellt wurde, ist der Aufruf von flush
oder close
nicht erforderlich. Im Gegensatz zu vielen Streams kann eine Output-Instanz durch Festlegen der Position oder Festlegen eines neuen Byte-Arrays oder Streams wiederverwendet werden.
Tipp: Da Output-Puffer bereits vorhanden sind, gibt es keinen Grund, Output in einen BufferedOutputStream zu leeren.
Der Ausgabekonstruktor mit Nullargument erstellt eine nicht initialisierte Ausgabe. Output setBuffer
muss aufgerufen werden, bevor Output verwendet werden kann.
Die Input-Klasse ist ein InputStream, der Daten aus einem Byte-Array-Puffer liest. Dieser Puffer kann direkt gesetzt werden, wenn das Lesen aus einem Byte-Array gewünscht ist. Wenn der Eingabe ein InputStream zugewiesen wird, füllt sie den Puffer aus dem Stream, wenn alle Daten im Puffer gelesen wurden. Input verfügt über viele Methoden zum effizienten Lesen von Grundelementen und Zeichenfolgen aus Bytes. Es bietet ähnliche Funktionen wie DataInputStream, BufferedInputStream, FilterInputStream und ByteArrayInputStream, alle in einer Klasse.
Tipp: Input bietet die gesamte Funktionalität von ByteArrayInputStream. Es gibt selten einen Grund, Input aus einem ByteArrayInputStream lesen zu lassen.
Wenn der Input close
aufgerufen wird, wird der InputStream des Input geschlossen, sofern vorhanden. Wenn nicht aus einem InputStream gelesen wird, ist es nicht notwendig, close
aufzurufen. Im Gegensatz zu vielen Streams kann eine Eingabeinstanz durch Festlegen der Position und des Grenzwerts oder durch Festlegen eines neuen Byte-Arrays oder InputStreams wiederverwendet werden.
Der Eingabekonstruktor mit dem Nullargument erstellt eine nicht initialisierte Eingabe. Input setBuffer
muss aufgerufen werden, bevor der Input verwendet werden kann.
Die Klassen ByteBufferOutput und ByteBufferInput funktionieren genau wie Output und Input, außer dass sie einen ByteBuffer anstelle eines Byte-Arrays verwenden.
Die Klassen UnsafeOutput, UnsafeInput, UnsafeByteBufferOutput und UnsafeByteBufferInput funktionieren genau wie ihre nicht unsicheren Gegenstücke, außer dass sie in vielen Fällen sun.misc.Unsafe für eine höhere Leistung verwenden. Um diese Klassen verwenden zu können, muss Util.unsafe
true sein.
Der Nachteil bei der Verwendung unsicherer Puffer besteht darin, dass die native Endianness und die Darstellung numerischer Typen des Systems, das die Serialisierung durchführt, Auswirkungen auf die serialisierten Daten haben. Beispielsweise schlägt die Deserialisierung fehl, wenn die Daten auf X86 geschrieben und auf SPARC gelesen werden. Wenn Daten mit einem unsicheren Puffer geschrieben werden, müssen sie außerdem mit einem unsicheren Puffer gelesen werden.
Der größte Leistungsunterschied bei unsicheren Puffern besteht bei großen primitiven Arrays, wenn keine Codierung mit variabler Länge verwendet wird. Die Codierung variabler Länge kann für die unsicheren Puffer oder nur für bestimmte Felder (bei Verwendung von FieldSerializer) deaktiviert werden.
Die IO-Klassen stellen Methoden zum Lesen und Schreiben von int- (varint) und long- (varlong) Werten variabler Länge bereit. Dies geschieht, indem das 8. Bit jedes Bytes verwendet wird, um anzuzeigen, ob weitere Bytes folgen, was bedeutet, dass ein Varint 1–5 Bytes und ein Varlong 1–9 Bytes verwendet. Die Verwendung einer Codierung mit variabler Länge ist zwar teurer, macht die serialisierten Daten jedoch viel kleiner.
Beim Schreiben eines Werts mit variabler Länge kann der Wert entweder für positive Werte oder sowohl für negative als auch positive Werte optimiert werden. Bei einer Optimierung für positive Werte werden beispielsweise 0 bis 127 in ein Byte geschrieben, 128 bis 16383 in zwei Bytes usw. Kleine negative Zahlen sind jedoch bei 5 Bytes der schlechteste Fall. Wenn diese Bereiche nicht positiv optimiert sind, werden sie um die Hälfte nach unten verschoben. Beispielsweise wird -64 bis 63 in ein Byte geschrieben, 64 bis 8191 und -65 bis -8192 in zwei Bytes usw.
Eingabe- und Ausgabepuffer bieten Methoden zum Lesen und Schreiben von Werten fester Größe oder variabler Länge. Es gibt auch Methoden, mit denen der Puffer entscheiden kann, ob ein Wert mit fester Größe oder mit variabler Länge geschrieben wird. Dadurch kann der Serialisierungscode sicherstellen, dass die Codierung mit variabler Länge für sehr häufige Werte verwendet wird, die die Ausgabe aufblähen würden, wenn eine feste Größe verwendet würde, während die Pufferkonfiguration weiterhin für alle anderen Werte entscheiden kann.
Verfahren | Beschreibung |
---|---|
writeInt(int) | Schreibt einen 4-Byte-Int. |
writeVarInt(int, boolean) | Schreibt ein 1–5 Byte großes int. |
writeInt(int, boolean) | Schreibt entweder ein 4- oder 1-5-Byte-Int (der Puffer entscheidet). |
writeLong(lang) | Schreibt eine 8 Byte lange Datei. |
writeVarLong(long, boolean) | Schreibt eine 1-9 Byte lange Datei. |
writeLong(long, boolean) | Schreibt entweder eine Länge von 8 oder 1-9 Byte (der Puffer entscheidet). |
Um die Codierung variabler Länge für alle Werte zu deaktivieren, müssten die Methoden writeVarInt
, writeVarLong
, readVarInt
und readVarLong
überschrieben werden.
Es kann nützlich sein, erst die Länge einiger Daten und dann die Daten zu schreiben. Wenn die Länge der Daten nicht im Voraus bekannt ist, müssen alle Daten gepuffert werden, um ihre Länge zu bestimmen. Anschließend kann die Länge und dann die Daten geschrieben werden. Die Verwendung eines einzigen großen Puffers würde das Streaming verhindern und möglicherweise einen unangemessen großen Puffer erfordern, was nicht ideal ist.
Chunked-Codierung löst dieses Problem durch die Verwendung eines kleinen Puffers. Wenn der Puffer voll ist, wird seine Länge und dann die Daten geschrieben. Dies ist ein Datenblock. Der Puffer wird geleert und dies wird so lange fortgesetzt, bis keine Daten mehr zum Schreiben vorhanden sind. Ein Chunk mit der Länge Null bezeichnet das Ende der Chunks.
Kryo stellt Klassen bereit, um die Verwendung der Chunked-Codierung zu vereinfachen. OutputChunked wird zum Schreiben von Chunked-Daten verwendet. Es erweitert Output und verfügt daher über alle praktischen Methoden zum Schreiben von Daten. Wenn der OutputChunked-Puffer voll ist, wird der Block in einen anderen OutputStream geleert. Die endChunk
-Methode wird verwendet, um das Ende einer Reihe von Chunks zu markieren.
OutputStream outputStream = new FileOutputStream ( "file.bin" );
OutputChunked output = new OutputChunked ( outputStream , 1024 );
// Write data to output...
output . endChunk ();
// Write more data to output...
output . endChunk ();
// Write even more data to output...
output . endChunk ();
output . close ();
Zum Lesen der Chunked-Daten wird InputChunked verwendet. Es erweitert Input und verfügt daher über alle praktischen Methoden zum Lesen von Daten. Beim Lesen scheint InputChunked das Ende der Daten zu erreichen, wenn es das Ende einer Reihe von Blöcken erreicht. Die nextChunks
-Methode geht zum nächsten Satz von Blöcken über, auch wenn nicht alle Daten aus dem aktuellen Satz von Blöcken gelesen wurden.
InputStream outputStream = new FileInputStream ( "file.bin" );
InputChunked input = new InputChunked ( inputStream , 1024 );
// Read data from first set of chunks...
input . nextChunks ();
// Read data from second set of chunks...
input . nextChunks ();
// Read data from third set of chunks...
input . close ();
Im Allgemeinen bieten Ausgabe und Eingabe eine gute Leistung. Unsichere Puffer funktionieren genauso gut oder besser, insbesondere bei primitiven Arrays, wenn ihre plattformübergreifenden Inkompatibilitäten akzeptabel sind. ByteBufferOutput und ByteBufferInput bieten eine etwas schlechtere Leistung, dies kann jedoch akzeptabel sein, wenn das endgültige Ziel der Bytes ein ByteBuffer sein muss.
Die Codierung mit variabler Länge ist langsamer als die Codierung mit festen Werten, insbesondere wenn sie von vielen Daten verwendet wird.
Bei der Chunked-Codierung wird ein Zwischenpuffer verwendet, sodass eine zusätzliche Kopie aller Bytes hinzugefügt wird. Dies allein mag akzeptabel sein, aber wenn es in einem wiedereintrittsfähigen Serialisierer verwendet wird, muss der Serialisierer für jedes Objekt ein OutputChunked oder InputChunked erstellen. Die Zuweisung und Müllsammlung dieser Puffer während der Serialisierung kann sich negativ auf die Leistung auswirken.
Kryo verfügt über drei Methodensätze zum Lesen und Schreiben von Objekten. Wenn die konkrete Klasse des Objekts nicht bekannt ist und das Objekt null sein könnte:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
Wenn die Klasse bekannt ist und das Objekt null sein könnte:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
Wenn die Klasse bekannt ist und das Objekt nicht null sein kann:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
Alle diese Methoden suchen zunächst nach dem geeigneten Serialisierer und verwenden diesen dann zum Serialisieren oder Deserialisieren des Objekts. Serialisierer können diese Methoden zur rekursiven Serialisierung aufrufen. Mehrere Verweise auf dasselbe Objekt und Zirkelverweise werden von Kryo automatisch verarbeitet.
Neben Methoden zum Lesen und Schreiben von Objekten bietet die Kryo-Klasse eine Möglichkeit, Serialisierer zu registrieren, Klassenkennungen effizient zu lesen und zu schreiben, Nullobjekte für Serialisierer zu verarbeiten, die keine Nullen akzeptieren können, und das Lesen und Schreiben von Objektverweisen (sofern aktiviert). Dadurch können sich Serialisierer auf ihre Serialisierungsaufgaben konzentrieren.
Beim Testen und Erkunden von Kryo-APIs kann es nützlich sein, ein Objekt in Bytes zu schreiben und diese Bytes dann wieder in ein Objekt einzulesen.
Kryo kryo = new Kryo ();
// Register all classes to be serialized.
kryo . register ( SomeClass . class );
SomeClass object1 = new SomeClass ();
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , object1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
SomeClass object2 = kryo . readObject ( input , SomeClass . class );
In diesem Beispiel beginnt die Ausgabe mit einem Puffer, der eine Kapazität von 1024 Bytes hat. Wenn mehr Bytes in die Ausgabe geschrieben werden, vergrößert sich der Puffer unbegrenzt. Der Output muss nicht geschlossen werden, da ihm kein OutputStream zugewiesen wurde. Der Eingang liest direkt aus dem byte[]
Puffer des Ausgangs.
Kryo unterstützt die Erstellung tiefer und flacher Kopien von Objekten durch direkte Zuweisung von einem Objekt zu einem anderen. Dies ist effizienter als die Serialisierung in Bytes und zurück in Objekte.
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
Alle verwendeten Serialisierer müssen das Kopieren unterstützen. Alle mit Kryo bereitgestellten Serialisierer unterstützen das Kopieren.
Wie bei der Serialisierung werden beim Kopieren mehrere Verweise auf dasselbe Objekt und Zirkelverweise von Kryo automatisch verarbeitet, wenn Verweise aktiviert sind.
Wenn Sie Kryo nur zum Kopieren verwenden, kann die Registrierung sicher deaktiviert werden.
Kryo getOriginalToCopyMap
kann nach dem Kopieren eines Objektdiagramms verwendet werden, um eine Karte von alten zu neuen Objekten zu erhalten. Die Karte wird durch Kryo reset
automatisch gelöscht und ist daher nur nützlich, wenn Kryo setAutoReset
false ist.
Standardmäßig sind Referenzen nicht aktiviert. Das heißt, wenn ein Objekt mehrmals in einem Objektdiagramm erscheint, wird es mehrmals geschrieben und als mehrere unterschiedliche Objekte deserialisiert. Wenn Referenzen deaktiviert sind, führen Zirkelverweise dazu, dass die Serialisierung fehlschlägt. Referenzen werden mit Kryo setReferences
für die Serialisierung und setCopyReferences
für das Kopieren aktiviert oder deaktiviert.
Wenn Referenzen aktiviert sind, wird vor jedem Objekt ein Varint geschrieben, wenn es zum ersten Mal im Objektdiagramm erscheint. Für nachfolgende Auftritte dieser Klasse innerhalb desselben Objektgraphen wird nur eine Variante geschrieben. Nach der Deserialisierung werden die Objektreferenzen wiederhergestellt, einschließlich aller Zirkelverweise. Die verwendeten Serialisierer müssen Referenzen unterstützen, indem sie Kryo reference
in Serializer read
aufrufen.
Das Aktivieren von Referenzen wirkt sich auf die Leistung aus, da jedes Objekt, das gelesen oder geschrieben wird, nachverfolgt werden muss.
Im Hintergrund verwaltet ein ReferenceResolver die Verfolgung gelesener oder geschriebener Objekte und stellt int-Referenz-IDs bereit. Es werden mehrere Implementierungen bereitgestellt:
ReferenceResolver useReferences(Class)
kann überschrieben werden. Es gibt einen booleschen Wert zurück, um zu entscheiden, ob Referenzen für eine Klasse unterstützt werden. Wenn eine Klasse keine Referenzen unterstützt, wird die Varint-Referenz-ID nicht vor Objekte dieses Typs geschrieben. Wenn eine Klasse keine Referenzen benötigt und Objekte dieses Typs häufig im Objektdiagramm erscheinen, kann die serialisierte Größe erheblich reduziert werden, indem Referenzen für diese Klasse deaktiviert werden. Der Standard-Referenz-Resolver gibt für alle primitiven Wrapper und Aufzählungen „false“ zurück. Abhängig von den zu serialisierenden Objektdiagrammen wird häufig auch für String und andere Klassen „false“ zurückgegeben.
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
Der Referenzresolver bestimmt die maximale Anzahl von Referenzen in einem einzelnen Objektdiagramm. Java-Array-Indizes sind auf Integer.MAX_VALUE
beschränkt, daher können Referenz-Resolver, die auf Arrays basierende Datenstrukturen verwenden, bei der Serialisierung von mehr als ~2 Milliarden Objekten zu einer java.lang.NegativeArraySizeException
führen. Kryo verwendet int-Klassen-IDs, sodass die maximale Anzahl von Referenzen in einem einzelnen Objektdiagramm auf den gesamten Bereich positiver und negativer Zahlen in einem int (~4 Milliarden) begrenzt ist.
Kryo getContext
gibt eine Karte zum Speichern von Benutzerdaten zurück. Die Kryo-Instanz steht allen Serialisierern zur Verfügung, sodass diese Daten für alle Serialisierer leicht zugänglich sind.
Kryo getGraphContext
ist ähnlich, wird jedoch gelöscht, nachdem jedes Objektdiagramm serialisiert oder deserialisiert wurde. Dies erleichtert die Verwaltung von Zuständen, die nur für den aktuellen Objektgraphen relevant sind. Dies kann beispielsweise verwendet werden, um einige Schemadaten zu schreiben, wenn eine Klasse zum ersten Mal in einem Objektdiagramm angetroffen wird. Ein Beispiel finden Sie unter „CompatibleFieldSerializer“.
Standardmäßig wird Kryo- reset
aufgerufen, nachdem jedes gesamte Objektdiagramm serialisiert wurde. Dadurch werden nicht registrierte Klassennamen im Klassen-Resolver, Verweise auf zuvor serialisierte oder deserialisierte Objekte im Referenz-Resolver zurückgesetzt und der Diagrammkontext gelöscht. Kryo setAutoReset(false)
kann verwendet werden, um das automatische reset
des Aufrufs zu deaktivieren, sodass dieser Zustand mehrere Objektdiagramme umfassen kann.
Kryo ist ein Framework zur Erleichterung der Serialisierung. Das Framework selbst erzwingt kein Schema und kümmert sich nicht darum, was oder wie Daten geschrieben oder gelesen werden. Serialisierer sind steckbar und entscheiden darüber, was gelesen und geschrieben wird. Viele Serialisierer werden standardmäßig zum Lesen und Schreiben von Daten auf verschiedene Arten bereitgestellt. Während die bereitgestellten Serialisierer die meisten Objekte lesen und schreiben können, können sie problemlos teilweise oder vollständig durch Ihre eigenen Serialisierer ersetzt werden.
Wenn Kryo eine Instanz eines Objekts schreiben möchte, muss es möglicherweise zuerst etwas schreiben, das die Klasse des Objekts identifiziert. Standardmäßig müssen alle Klassen, die Kryo lesen oder schreiben wird, vorher registriert werden. Die Registrierung stellt eine int-Klassen-ID, den für die Klasse zu verwendenden Serialisierer und den Objektinstanziator bereit, der zum Erstellen von Instanzen der Klasse verwendet wird.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Während der Deserialisierung müssen die registrierten Klassen genau dieselben IDs haben wie während der Serialisierung. Bei der Registrierung wird einer Klasse die nächst verfügbare, niedrigste Ganzzahl-ID zugewiesen, was bedeutet, dass die Reihenfolge der registrierten Klassen wichtig ist. Die Klassen-ID kann optional explizit angegeben werden, um die Reihenfolge unwichtig zu machen:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
Die Klassen-IDs -1 und -2 sind reserviert. Die Klassen-IDs 0–8 werden standardmäßig für primitive Typen und String verwendet, obwohl diese IDs für andere Zwecke verwendet werden können. Die IDs werden als positiv optimierte Varianten geschrieben und sind daher am effizientesten, wenn es sich um kleine, positive Ganzzahlen handelt. Negative IDs werden nicht effizient serialisiert.
Unter der Decke übernimmt ein ClassResolver das eigentliche Lesen und Schreiben von Bytes zur Darstellung einer Klasse. Die Standardimplementierung reicht in den meisten Fällen aus, kann jedoch ersetzt werden, um anzupassen, was passiert, wenn eine Klasse registriert wird, was passiert, wenn während der Serialisierung auf eine nicht registrierte Klasse gestoßen wird, und was zur Darstellung einer Klasse gelesen und geschrieben wird.
Kryo kann so konfiguriert werden, dass eine Serialisierung möglich ist, ohne dass Klassen im Voraus registriert werden müssen.
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Eine gemischte Nutzung von angemeldeten und nicht angemeldeten Kursen ist möglich. Nicht angemeldete Kurse haben zwei große Nachteile:
Wenn Sie Kryo nur zum Kopieren verwenden, kann die Registrierung sicher deaktiviert werden.
Wenn keine Registrierung erforderlich ist, kann Kryo setWarnUnregisteredClasses
aktiviert werden, um eine Nachricht zu protokollieren, wenn eine nicht registrierte Klasse gefunden wird. Damit lässt sich ganz einfach eine Liste aller nicht registrierten Klassen erhalten. Kryo unregisteredClassMessage
kann überschrieben werden, um die Protokollnachricht anzupassen oder andere Aktionen durchzuführen.
Bei der Registrierung einer Klasse kann optional eine Serializer-Instanz angegeben werden. Während der Deserialisierung müssen die registrierten Klassen genau dieselben Serialisierer und Serialisiererkonfigurationen haben wie während der Serialisierung.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
Wenn kein Serialisierer angegeben ist oder wenn eine nicht registrierte Klasse angetroffen wird, wird automatisch ein Serialisierer aus einer Liste von „Standardserialisierern“ ausgewählt, der eine Klasse einem Serialisierer zuordnet. Viele Standard-Serialisierer wirken sich nicht auf die Serialisierungsleistung aus, daher verfügt Kryo standardmäßig über 50+ Standard-Serialisierer für verschiedene JRE-Klassen. Zusätzliche Standard-Serialisierer können hinzugefügt werden:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Dadurch wird eine SomeSerializer-Instanz erstellt, wenn SomeClass oder eine andere Klasse, die SomeClass erweitert oder implementiert, registriert wird.
Standard-Serialisierer sind so sortiert, dass spezifischere Klassen zuerst abgeglichen werden, ansonsten jedoch in der Reihenfolge, in der sie hinzugefügt werden. Die Reihenfolge, in der sie hinzugefügt werden, kann für Schnittstellen relevant sein.
Wenn keine Standard-Serialisierer mit einer Klasse übereinstimmen, wird der globale Standard-Serialisierer verwendet. Der globale Standard-Serializer ist standardmäßig auf FieldSerializer eingestellt, kann aber geändert werden. Normalerweise ist der globale Serialisierer einer, der viele verschiedene Typen verarbeiten kann.
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
Mit diesem Code wird TaggedFieldSerializer verwendet, sofern kein Standard-Serialisierer mit SomeClass übereinstimmt.
Eine Klasse kann auch die DefaultSerializer-Annotation verwenden, die anstelle der Auswahl eines der Standard-Serializer von Kryo verwendet wird:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
Für maximale Flexibilität kann Kryo getDefaultSerializer
überschrieben werden, um benutzerdefinierte Logik für die Auswahl und Instanziierung eines Serialisierers zu implementieren.
Die Methode addDefaultSerializer(Class, Class)
ermöglicht keine Konfiguration des Serializers. Anstelle einer Serializer-Klasse kann eine Serializer-Factory festgelegt werden, sodass die Factory jede Serializer-Instanz erstellen und konfigurieren kann. Für gängige Serialisierer werden Fabriken bereitgestellt, häufig mit einer getConfig
-Methode zum Konfigurieren der erstellten Serialisierer.
Kryo kryo = new Kryo ();
TaggedFieldSerializerFactory defaultFactory = new TaggedFieldSerializerFactory ();
defaultFactory . getConfig (). setReadUnknownTagData ( true );
kryo . setDefaultSerializer ( defaultFactory );
FieldSerializerFactory someClassFactory = new FieldSerializerFactory ();
someClassFactory . getConfig (). setFieldsCanBeNull ( false );
kryo . register ( SomeClass . class , someClassFactory );
Die Serialisierungsfabrik verfügt über eine isSupported(Class)
-Methode, die es ihr ermöglicht, die Verarbeitung einer Klasse abzulehnen, selbst wenn sie ansonsten mit der Klasse übereinstimmt. Dadurch kann eine Fabrik nach mehreren Schnittstellen suchen oder andere Logik implementieren.
Während einige Serialisierer für eine bestimmte Klasse gedacht sind, können andere viele verschiedene Klassen serialisieren. Serialisierer können Kryo newInstance(Class)
verwenden, um eine Instanz einer beliebigen Klasse zu erstellen. Dies erfolgt durch Nachschlagen der Registrierung für die Klasse und anschließende Verwendung des ObjectInstantiator der Registrierung. Der Instanziator kann bei der Registrierung angegeben werden.
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Wenn die Registrierung keinen Instanziator hat, wird einer von Kryo newInstantiator
bereitgestellt. Um die Erstellung von Objekten anzupassen, kann Kryo newInstantiator
überschrieben oder eine InstantiatorStrategy bereitgestellt werden.
Kryo bietet DefaultInstantiatorStrategy, das mithilfe von ReflectASM Objekte erstellt, um einen Konstruktor ohne Argumente aufzurufen. Wenn dies nicht möglich ist, wird mithilfe der Reflektion ein Konstruktor mit Nullargumenten aufgerufen. Wenn dies ebenfalls fehlschlägt, wird entweder eine Ausnahme ausgelöst oder es wird eine Fallback-InstantiatorStrategy versucht. Reflection verwendet setAccessible
, daher kann ein privater Null-Argument-Konstruktor eine gute Möglichkeit sein, Kryo die Erstellung von Instanzen einer Klasse zu ermöglichen, ohne die öffentliche API zu beeinträchtigen.
DefaultInstantiatorStrategy ist die empfohlene Methode zum Erstellen von Objekten mit Kryo. Es führt Konstruktoren aus, genau wie es mit Java-Code geschehen würde. Zur Erzeugung von Objekten können auch alternative, außersprachliche Mechanismen genutzt werden. Die Objenesis StdInstantiatorStrategy verwendet JVM-spezifische APIs, um eine Instanz einer Klasse zu erstellen, ohne überhaupt einen Konstruktor aufzurufen. Dies zu verwenden ist gefährlich, da die meisten Klassen erwarten, dass ihre Konstruktoren aufgerufen werden. Das Erstellen des Objekts unter Umgehung seiner Konstruktoren kann dazu führen, dass das Objekt in einem nicht initialisierten oder ungültigen Zustand verbleibt. Klassen müssen so gestaltet sein, dass sie auf diese Weise erstellt werden können.
Kryo kann so konfiguriert werden, dass es zuerst DefaultInstantiatorStrategy ausprobiert und dann bei Bedarf auf StdInstantiatorStrategy zurückgreift.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
Eine weitere Option ist SerializingInstantiatorStrategy, die den in Java integrierten Serialisierungsmechanismus zum Erstellen einer Instanz verwendet. Damit muss die Klasse java.io.Serializable implementieren und der erste Null-Argument-Konstruktor in einer Superklasse wird aufgerufen. Dies umgeht auch Konstruktoren und ist daher aus den gleichen Gründen wie StdInstantiatorStrategy gefährlich.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
Alternativ stellen einige generische Serialisierer Methoden bereit, die überschrieben werden können, um die Objekterstellung für einen bestimmten Typ anzupassen, anstatt Kryo newInstance
aufzurufen.
kryo . register ( SomeClass . class , new FieldSerializer ( kryo , SomeClass . class ) {
protected T create ( Kryo kryo , Input input , Class <? extends T > type ) {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Einige Serialisierer bieten eine writeHeader
-Methode, die überschrieben werden kann, um Daten, die beim create
benötigt werden, zum richtigen Zeitpunkt zu schreiben.
static public class TreeMapSerializer extends MapSerializer < TreeMap > {
protected void writeHeader ( Kryo kryo , Output output , TreeMap map ) {
kryo . writeClassAndObject ( output , map . comparator ());
}
protected TreeMap create ( Kryo kryo , Input input , Class <? extends TreeMap > type , int size ) {
return new TreeMap (( Comparator ) kryo . readClassAndObject ( input ));
}
}
Wenn ein Serialisierer writeHeader
nicht bereitstellt, können die Daten für create
in write
geschrieben werden.
static public class SomeClassSerializer extends FieldSerializer < SomeClass > {
public SomeClassSerializer ( Kryo kryo ) {
super ( kryo , SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
output . writeInt ( object . value );
}
protected SomeClass create ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
return new SomeClass ( input . readInt ());
}
}
Selbst wenn ein Serialisierer die erwartete Klasse für einen Wert kennt (z. B. die Klasse eines Felds), muss der Serialisierer zuerst die Klassen-ID und dann den Wert schreiben, wenn die konkrete Klasse des Werts nicht endgültig ist. Endgültige Klassen können effizienter serialisiert werden, da sie nicht polymorph sind.
Kryo isFinal
wird verwendet, um festzustellen, ob eine Klasse endgültig ist. Diese Methode kann überschrieben werden, um auch für Typen, die nicht endgültig sind, „true“ zurückzugeben. Wenn eine Anwendung beispielsweise ArrayList häufig, aber nie eine ArrayList-Unterklasse verwendet, könnte die Behandlung von ArrayList als endgültig dazu führen, dass FieldSerializer 1–2 Bytes pro ArrayList-Feld einspart.
Kryo kann mit einigen Einschränkungen Java 8+-Closures serialisieren, die java.io.Serializable implementieren. Auf einer JVM serialisierte Schließungen können auf einer anderen JVM möglicherweise nicht deserialisiert werden.
Kryo isClosure
wird verwendet, um zu bestimmen, ob eine Klasse ein Abschluss ist. Wenn dies der Fall ist, wird „ClosureSerializer.Closure“ verwendet, um die Klassenregistrierung anstelle der Klasse des Abschlusses zu finden. Um Abschlüsse zu serialisieren, müssen die folgenden Klassen registriert werden: ClosureSerializer.Closure, Object[] und Class. Darüber hinaus muss die Erfassungsklasse des Abschlusses registriert werden.
kryo . register ( Object []. class );
kryo . register ( Class . class );
kryo . register ( ClosureSerializer . Closure . class , new ClosureSerializer ());
kryo . register ( CapturingClass . class );
Callable < Integer > closure1 = ( Callable < Integer > & java . io . Serializable )( () -> 72363 );
Output output = new Output ( 1024 , - 1 );
kryo . writeObject ( output , closure1 );
Input input = new Input ( output . getBuffer (), 0 , output . position ());
Callable < Integer > closure2 = ( Callable < Integer >) kryo . readObject ( input , ClosureSerializer . Closure . class );
Das Serialisieren von Abschlüssen, die Serializable nicht implementieren, ist mit einigem Aufwand möglich.
Kryo unterstützt Streams, daher ist es einfach, Komprimierung oder Verschlüsselung für alle serialisierten Bytes zu verwenden:
OutputStream outputStream = new DeflaterOutputStream ( new FileOutputStream ( "file.bin" ));
Output output = new Output ( outputStream );
Kryo kryo = new Kryo ();
kryo . writeObject ( output , object );
output . close ();
Bei Bedarf kann ein Serialisierer verwendet werden, um die Bytes nur für eine Teilmenge der Bytes für einen Objektgraphen zu komprimieren oder zu verschlüsseln. Siehe beispielsweise DeflateSerializer oder BlowfishSerializer. Diese Serialisierer umschließen einen anderen Serialisierer, um die Bytes zu kodieren und zu dekodieren.
Die abstrakte Klasse Serializer definiert Methoden, um von Objekten zu Bytes und von Bytes zu Objekten zu wechseln.
public class ColorSerializer extends Serializer < Color > {
public void write ( Kryo kryo , Output output , Color color ) {
output . writeInt ( color . getRGB ());
}
public Color read ( Kryo kryo , Input input , Class <? extends Color > type ) {
return new Color ( input . readInt ());
}
}
Serializer verfügt nur über zwei Methoden, die implementiert werden müssen. write
schreibt das Objekt als Bytes auf den Ausgang. read
erstellt eine neue Instanz des Objekts und liest aus der Eingabe, um es zu füllen.
Wenn Kryo zum Lesen eines verschachtelten Objekts beim Serializer- read
verwendet wird, muss die Kryo reference
zuerst mit dem übergeordneten Objekt aufgerufen werden, wenn es für das verschachtelte Objekt möglich ist, auf das übergeordnete Objekt zu verweisen. Es ist nicht erforderlich, die Kryo reference
aufzurufen, wenn die verschachtelten Objekte möglicherweise nicht auf das übergeordnete Objekt verweisen können, wenn Kryo nicht für verschachtelte Objekte verwendet wird oder wenn keine Referenzen verwendet werden. Wenn verschachtelte Objekte denselben Serialisierer verwenden können, muss der Serialisierer reentrant sein.
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
SomeClass object = new SomeClass ();
kryo . reference ( object );
// Read objects that may reference the SomeClass instance.
object . someField = kryo . readClassAndObject ( input );
return object ;
}
Serialisierer sollten normalerweise nicht direkt auf andere Serialisierer zurückgreifen, stattdessen sollten die Lese- und Schreibmethoden von Kryo verwendet werden. Dadurch kann Kryo die Serialisierung orchestrieren und Funktionen wie Referenzen und Nullobjekte verarbeiten. Manchmal weiß ein Serialisierer, welchen Serialisierer er für ein verschachteltes Objekt verwenden soll. In diesem Fall sollten die Lese- und Schreibmethoden von Kryo verwendet werden, die einen Serialisierer akzeptieren.
Wenn das Objekt null sein könnte:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
Wenn das Objekt nicht null sein kann:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
Während der Serialisierung stellt Kryo getDepth
die aktuelle Tiefe des Objektdiagramms bereit.
Wenn eine Serialisierung fehlschlägt, kann eine KryoException mit Serialisierungs-Trace-Informationen darüber ausgelöst werden, wo im Objektdiagramm die Ausnahme aufgetreten ist. Bei Verwendung verschachtelter Serialisierer kann KryoException abgefangen werden, um Serialisierungs-Trace-Informationen hinzuzufügen.
Object object = ...
Field [] fields = ...
for ( Field field : fields ) {
try {
// Use other serializers to serialize each field.
} catch ( KryoException ex ) {
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
} catch ( Throwable t ) {
KryoException ex = new KryoException ( t );
ex . addTrace ( field . getName () + " (" + object . getClass (). getName () + ")" );
throw ex ;
}
}
Die von Kryo bereitgestellten Serialisierer verwenden den Aufrufstapel bei der Serialisierung verschachtelter Objekte. Kryo minimiert Stapelaufrufe, bei extrem tiefen Objektdiagrammen kann es jedoch zu einem Stapelüberlauf kommen. Dies ist ein häufiges Problem bei den meisten Serialisierungsbibliotheken, einschließlich der integrierten Java-Serialisierung. Die Stapelgröße kann mit -Xss
erhöht werden. Beachten Sie jedoch, dass dies für alle Threads gilt. Große Stapelgrößen in einer JVM mit vielen Threads können viel Speicher verbrauchen.
Kryo setMaxDepth
kann verwendet werden, um die maximale Tiefe eines Objektdiagramms zu begrenzen. Dadurch kann verhindert werden, dass bösartige Daten einen Stapelüberlauf verursachen.
Standardmäßig erhalten Serialisierer niemals eine Null, stattdessen schreibt Kryo nach Bedarf ein Byte, um Null oder Nicht-Null anzugeben. Wenn ein Serializer effizienter sein kann, indem er Nullen selbst verarbeitet, kann er Serializer setAcceptsNull(true)
aufrufen. Dies kann auch verwendet werden, um das Schreiben des NULL-bezeichnenden Bytes zu vermeiden, wenn bekannt ist, dass alle vom Serialisierer verarbeiteten Instanzen niemals NULL sein werden.
Kryo getGenerics
stellt generische Typinformationen bereit, damit Serialisierer effizienter arbeiten können. Dies wird am häufigsten verwendet, um das Schreiben der Klasse zu vermeiden, wenn die Typparameterklasse endgültig ist.
Die generische Typinferenz ist standardmäßig aktiviert und kann mit Kryo setOptimizedGenerics(false)
deaktiviert werden. Das Deaktivieren der Generika-Optimierung kann die Leistung auf Kosten einer größeren serialisierten Größe steigern.
Wenn die Klasse einen einzelnen Typparameter hat, gibt nextGenericClass
den Typparameter class zurück oder null, wenn keiner vorhanden ist. Nach dem Lesen oder Schreiben aller verschachtelten Objekte muss popGenericType
aufgerufen werden. Ein Beispiel finden Sie unter CollectionSerializer.
public class SomeClass < T > {
public T value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
Für eine Klasse mit mehreren Typparametern gibt nextGenericTypes
ein Array von GenericType-Instanzen zurück und resolve
wird verwendet, um die Klasse für jeden GenericType abzurufen. Nach dem Lesen oder Schreiben aller verschachtelten Objekte muss popGenericType
aufgerufen werden. Ein Beispiel finden Sie unter MapSerializer.
public class SomeClass < K , V > {
public K key ;
public V value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
kryo . writeObjectOrNull ( output , object . key , serializer );
} else
kryo . writeClassAndObject ( output , object . key );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
object . key = kryo . readObjectOrNull ( input , keyClass , serializer );
} else
object . key = kryo . readClassAndObject ( input );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
Bei Serialisierern, die Typparameterinformationen für verschachtelte Objekte im Objektdiagramm übergeben (etwas fortgeschrittenere Verwendung), wird zunächst GenericsHierarchy verwendet, um die Typparameter für eine Klasse zu speichern. Während der Serialisierung wird Generics pushTypeVariables
aufgerufen, bevor generische Typen aufgelöst werden (falls vorhanden). Wenn >0 zurückgegeben wird, muss darauf Generics popTypeVariables
folgen. Ein Beispiel finden Sie unter FieldSerializer.
public class SomeClass < T > {
T value ;
List < T > list ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
private final GenericsHierarchy genericsHierarchy ;
public SomeClassSerializer () {
genericsHierarchy = new GenericsHierarchy ( SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . writeClassAndObject ( output , object . list );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
object . list = ( List ) kryo . readClassAndObject ( input );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
return object ;
}
}
Anstatt einen Serialisierer zu verwenden, kann eine Klasse ihre eigene Serialisierung durchführen, indem sie KryoSerializable implementiert (ähnlich wie java.io.Externalizable).
public class SomeClass implements KryoSerializable {
private int value ;
public void write ( Kryo kryo , Output output ) {
output . writeInt ( value , false );
}
public void read ( Kryo kryo , Input input ) {
value = input . readInt ( false );
}
}
Offensichtlich muss die Instanz bereits erstellt sein, bevor read
aufgerufen werden kann, sodass die Klasse ihre eigene Erstellung nicht steuern kann. Eine KryoSerializable-Klasse verwendet den Standard-Serialisierer KryoSerializableSerializer, der Kryo newInstance
verwendet, um eine neue Instanz zu erstellen. Es ist einfach, einen eigenen Serialisierer zu schreiben, um den Prozess anzupassen, Methoden vor oder nach der Serialisierung aufzurufen usw.
Serialisierer unterstützen das Kopieren nur, wenn copy
überschrieben wird. Ähnlich wie Serializer read
enthält diese Methode die Logik zum Erstellen und Konfigurieren der Kopie. Genau wie read
muss die Kryo- reference
aufgerufen werden, bevor Kryo zum Kopieren untergeordneter Objekte verwendet wird, wenn eines der untergeordneten Objekte auf das übergeordnete Objekt verweisen könnte.
class SomeClassSerializer extends Serializer < SomeClass > {
public SomeClass copy ( Kryo kryo , SomeClass original ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = original . intValue ;
copy . object = kryo . copy ( original . object );
return copy ;
}
}
Anstatt einen Serialisierer zu verwenden, können Klassen KryoCopyable implementieren, um ihr eigenes Kopieren durchzuführen:
public class SomeClass implements KryoCopyable < SomeClass > {
public SomeClass copy ( Kryo kryo ) {
SomeClass copy = new SomeClass ();
kryo . reference ( copy );
copy . intValue = intValue ;
copy . object = kryo . copy ( object );
return copy ;
}
}
Der Serializer setImmutable(true)
kann verwendet werden, wenn der Typ unveränderlich ist. In diesem Fall muss die Serializer- copy
nicht implementiert werden – die Standard copy
gibt das ursprüngliche Objekt zurück.
Für die Versionsnummerierung von Kryo gelten die folgenden Faustregeln:
Das Upgrade einer Abhängigkeit ist ein bedeutendes Ereignis, aber eine Serialisierungsbibliothek ist anfälliger für Bruch als die meisten Abhängigkeiten. Überprüfen Sie beim Upgrade von Kryo die Versionsunterschiede und testen Sie die neue Version in Ihren eigenen Anwendungen gründlich. Wir versuchen, es so sicher und einfach wie möglich zu machen.
Die standardmäßig bereitgestellten Kryo -Serialisierer gehen davon aus, dass Java zur Deserialisierung verwendet wird, sodass sie das geschriebene Format nicht explizit definieren. Serialisierer könnten mit einem standardisierten Format geschrieben werden, das von anderen Sprachen leichter gelesen wird. Dies wird jedoch standardmäßig nicht bereitgestellt.
Für einige Anforderungen, wie z. B. eine langfristige Speicherung serialisierter Bytes, kann es wichtig sein, wie Serialisierungsänderungen Änderungen an Klassen behandeln. Dies ist als Forward -Kompatibilität (durch neuere Klassen serialisierte Bytes) und Rückwärtskompatibilität (Lesen von Bytes, die durch ältere Klassen serialisiert werden). Kryo liefert einige generische Serialisierer, die unterschiedliche Ansätze für die Umgang mit Kompatibilität verfolgen. Zusätzliche Serialisierer können problemlos für die Vorwärts- und Rückwärtskompatibilität entwickelt werden, z. B. einen Serialisierer, der ein externes, handgeschriebenes Schema verwendet.
Wenn sich eine Klasse mehr ändert, als ihr Serializer verarbeiten kann, kann ein Serializer geschrieben werden, um die Daten in eine andere Klasse zu übertragen. Alle Verwendung der alten Klasse im Anwendungscode sollten durch die neue Klasse ersetzt werden. Die alte Klasse wird ausschließlich für diesen Serializer aufbewahrt.
kryo . register ( OldClass . class , new TaggedFieldSerializer ( kryo , OldClass . class ) {
public Object read ( Kryo kryo , Input input , Class type ) {
OldClass oldObject = ( OldClass ) super . read ( kryo , input , OldClass . class );
NewClass newObject = new NewClass ();
// Use data from the old class to populate the instance of the new class and return it.
return newObject ;
}
});
kryo . register ( NewClass . class );
Kryo bietet vielen Serialisierern verschiedene Konfigurationsoptionen und Kompatibilitätsniveaus. Zusätzliche Serialisierer finden Sie im Schwesterprojekt von Kryo-Serializern, bei dem Serialisierer auf private APIs zugreifen oder bei allen JVMs ansonsten nicht vollkommen sicher sind. Weitere Serialisierer finden Sie im Abschnitt Links.
FieldSerializer eignet sich durch Serialisierung jedes nicht-transienten Feldes. Es kann Pojos und viele andere Klassen ohne Konfiguration serialisieren. Alle nicht öffentlichen Felder werden standardmäßig geschrieben und gelesen. Daher ist es wichtig, jede serialisierte Klasse zu bewerten. Wenn Felder öffentlich sind, kann die Serialisierung schneller sein.
FieldSerializer ist effizient, indem nur die Felddaten ohne Schema -Informationen geschrieben werden, wobei die Java -Klassendateien als Schema verwendet werden. Es unterstützt das Hinzufügen, Entfernen oder Ändern der Feldertypen nicht, ohne zuvor serialisierte Bytes ungültig zu machen. Das Umbenennen von Feldern ist nur zulässig, wenn es die alphabetische Reihenfolge der Felder nicht ändert.
Die Kompatibilitätsnachteile von FieldSerializer können in vielen Situationen akzeptabel sein, z. In vielen Fällen ist TaggedFieldSerializer eine bessere Wahl.
Einstellung | Beschreibung | Standardwert |
---|---|---|
fieldsCanBeNull | Wenn falsch ist, wird davon ausgegangen, dass keine Feldwerte null sind, die 0-1 Byte pro Feld sparen können. | WAHR |
setFieldsAsAccessible | Wenn es zutrifft, werden alle nicht-transienten Felder (einschließlich privater Felder) bei Bedarf serialisiert und setAccessible . Wenn falsch, werden nur Felder in der öffentlichen API serialisiert. | WAHR |
ignoreSyntheticFields | Wenn wahr, werden synthetische Felder (vom Compiler für den Scoping erzeugt) serialisiert. | FALSCH |
fixedFieldTypes | Wenn es zutrifft, wird davon ausgegangen, dass der Betontyp jedes Feldwerts dem Feldtyp entspricht. Dadurch werden die Klassen -ID für Feldwerte geschrieben. | FALSCH |
copyTransient | Wenn wahr, werden alle Transientenfelder kopiert. | WAHR |
serializeTransient | Wenn wahr, werden Transient Felder serialisiert. | FALSCH |
variableLengthEncoding | Wenn wahr, werden variable Längenwerte für int- und lange Felder verwendet. | WAHR |
extendedFieldNames | Wenn die Feldnamen durch ihre deklarierende Klasse vorangestellt werden. Dies kann Konflikte vermeiden, wenn eine Unterklasse ein Feld mit demselben Namen wie eine Superklasse hat. | FALSCH |
FieldSerializer stellt die serialisierten Felder bereit. Felder können entfernt werden, sodass sie nicht serialisiert werden. Felder können so konfiguriert werden, dass die Serialierung effizienter wird.
FieldSerializer fieldSerializer = ...
fieldSerializer . removeField ( "id" ); // Won't be serialized.
CachedField nameField = fieldSerializer . getField ( "name" );
nameField . setCanBeNull ( false );
CachedField someClassField = fieldSerializer . getField ( "someClass" );
someClassField . setClass ( SomeClass . class , new SomeClassSerializer ());
Einstellung | Beschreibung | Standardwert |
---|---|---|
canBeNull | Wenn dies falsch ist, wird angenommen, dass der Feldwert niemals null ist, was 0-1 Byte speichern kann. | WAHR |
valueClass | Legt die Betonklasse und den Serializer fest, um den Feldwert zu verwenden. Dies beseitigt die Notwendigkeit, die Klassen -ID für den Wert zu schreiben. Wenn die Klasse des Feldwerts eine primitive, primitive Wrapper oder endgültig ist, stand diese Einstellung standardmäßig in die Feldklasse. | null |
serializer | Legt den Serialisierer für den Feldwert fest. Wenn der Serializer festgelegt ist, forderten einige Serialisierer, dass die Wertklasse ebenfalls festgelegt werden muss. Wenn NULL, wird der bei Kryo für die Feldwert registrierte Serialisierer verwendet. | null |
variableLengthEncoding | Wenn wahr, werden variable Längenwerte verwendet. Dies gilt nur für INT- oder lange Felder. | WAHR |
optimizePositive | Wenn wahr, werden positive Werte für variable Längenwerte optimiert. Dies gilt nur für INT- oder lange Felder, wenn die Codierung der variablen Länge verwendet wird. | WAHR |
Anmerkungen können verwendet werden, um die Serialisierer für jedes Feld zu konfigurieren.
Anmerkung | Beschreibung |
---|---|
@Bind | Legt die Cachedfield -Einstellungen für jedes Feld fest. |
@CollectionBind | Legt die Einstellungen für CollectionSerializer für Sammelfelder fest. |
@MapBind | Legt die Mapserializer -Einstellungen für Kartenfelder fest. |
@NotNull | Markiert ein Feld, das nie null ist. |
public class SomeClass {
@ NotNull
@ Bind ( serializer = StringSerializer . class , valueClass = String . class , canBeNull = false )
Object stringField ;
@ Bind ( variableLengthEncoding = false )
int intField ;
@ BindMap (
keySerializer = StringSerializer . class ,
valueSerializer = IntArraySerializer . class ,
keyClass = String . class ,
valueClass = int []. class ,
keysCanBeNull = false )
Map map ;
@ BindCollection (
elementSerializer = LongArraySerializer . class ,
elementClass = long []. class ,
elementsCanBeNull = false )
Collection collection ;
}
VersionFieldSerializer erweitert FieldSerializer und bietet Rückwärtskompatibilität. Dies bedeutet, dass Felder hinzugefügt werden können, ohne zuvor serialisierte Bytes ungültig zu machen. Das Entfernen, Umbenennen oder Ändern eines Feldes wird nicht unterstützt.
Wenn ein Feld hinzugefügt wird, muss die @Since(int)
Annotation angezeigt werden, um die Version anzugeben, die hinzugefügt wurde, um mit zuvor serialisierten Bytes kompatibel zu sein. Der Annotationswert darf sich niemals ändern.
VersionfieldSerializer fügt FieldSerializer nur sehr wenig Overhead hinzu: eine einzelne zusätzliche Varint.
Einstellung | Beschreibung | Standardwert |
---|---|---|
compatible | Wenn falsch, wird eine Ausnahme beim Lesen eines Objekts mit einer anderen Version ausgelöst. Die Version eines Objekts ist die maximale Version eines beliebigen Feldes. | WAHR |
VersionFieldSerializer erbt auch alle Einstellungen von FieldSerializer.
TaggedFieldSerializer erweitert FieldSerializer, um Rückwärtskompatibilität und optionale Vorwärtskompatibilität bereitzustellen. Dies bedeutet, dass Felder hinzugefügt oder umbenannt und optional entfernt werden können, ohne zuvor serialisierte Bytes ungültig zu machen. Das Ändern der Art eines Feldes wird nicht unterstützt.
Es werden nur Felder mit einer @Tag(int)
Annotation serialisiert. Feld -Tag -Werte müssen sowohl innerhalb einer Klasse als auch in allen Superklassen eindeutig sein. Eine Ausnahme wird ausgelöst, wenn doppelte Tag -Werte auftreten.
Die Vorwärts- und Rückwärtskompatibilitäts- und Serialisierungsleistung hängt von den Einstellungen readUnknownTagData
und chunkedEncoding
ab. Zusätzlich wird vor jedem Feld eine Varint für den Tag -Wert geschrieben.
Wenn readUnknownTagData
und chunkedEncoding
falsch sind, dürfen Felder nicht entfernt werden, aber die @Deprecated
Annotation kann angewendet werden. Veraltete Felder werden beim Lesen alter Bytes gelesen, aber nicht in neue Bytes geschrieben. Klassen können sich entwickeln, indem sie die Werte von veralteten Feldern lesen und an anderer Stelle schreiben. Felder können umbenannt und/oder privat gemacht werden, um Unordnung in der Klasse zu reduzieren (z. B. ignored1
, ignored2
).
TaggedFieldSerializer (mit readUnknownTagData
und chunkedEncoding
False) ist der empfohlene Serializer für die meisten Klassen, in denen Felder kommentiert werden können. Es ermöglicht es Klassen, sich weiterzuentwickeln und Felder aus den serialisierten Daten (durch Abschreibungen) zu entfernen, wodurch die Bedürfnisse der meisten Anwendungen erfüllt werden, ohne die serialisierte Größe viel hinzuzufügen.
Einstellung | Beschreibung | Standardwert |
---|---|---|
readUnknownTagData | Wenn Falsch und ein unbekanntes Tag auftreten, wird eine Ausnahme ausgelöst oder, wenn chunkedEncoding wahr ist, werden die Daten übersprungen.Wenn es zutrifft, wird die Klasse für jeden Feldwert vor dem Wert geschrieben. Wenn ein unbekanntes Tag auftritt, wird ein Versuch, die Daten zu lesen, durchgeführt. Dies wird verwendet, um die Daten zu überspringen, und wenn Referenzen aktiviert sind, können alle anderen Werte in der Objektgrafik verweisen, dass Daten weiterhin deserialisiert werden können. Wenn das Lesen der Daten fehlschlägt (z. B. die Klasse ist unbekannt oder entfernt), wird eine Ausnahme geworfen oder, wenn chunkedEncoding wahr ist, werden die Daten übersprungen.In beiden Fällen werden Referenzen in den übersprungenen Daten nicht gelesen und die weitere Deserialisierung werden möglicherweise die falschen Referenzen erhalten, wenn die Daten übersprungen sind und Referenzen aktiviert sind. | FALSCH |
chunkedEncoding | Wenn es zutrifft, werden Felder mit Chunked -Codierung geschrieben, damit unbekannte Felddaten übersprungen werden können. Dies wirkt sich auf die Leistung aus. | FALSCH |
chunkSize | Die maximale Größe jedes Chunks für die Chunked -Codierung. | 1024 |
TaggedFieldSerializer erbt auch alle Einstellungen von FieldSerializer.
CompatibleFieldSerializer erweitert FieldSerializer auf die Vorwärts- und Rückwärtskompatibilität. Dies bedeutet, dass Felder hinzugefügt oder entfernt werden können, ohne zuvor serialisierte Bytes ungültig zu machen. Das Umbenennen oder Ändern eines Feldes wird nicht unterstützt. Wie FieldSerializer kann er die meisten Klassen serialisieren, ohne Anmerkungen zu benötigen.
Die Vorwärts- und Rückwärtskompatibilitäts- und Serialisierungsleistung hängt von den Einstellungen readUnknownFieldData
und chunkedEncoding
ab. Darüber hinaus wird das erste Mal, dass die Klasse in den serialisierten Bytes auftritt, ein einfaches Schema mit den Feldnamenketten geschrieben. Da Felddaten namentlich identifiziert werden, müssen extendedFieldNames
, wenn eine Superklasse ein Feld mit demselben Namen wie eine Unterklasse hat, wahr sein.
Einstellung | Beschreibung | Standardwert |
---|---|---|
readUnknownFieldData | Wenn Falsch und ein unbekanntes Feld auftreten, wird eine Ausnahme ausgelöst oder, wenn chunkedEncoding wahr ist, werden die Daten übersprungen.Wenn es zutrifft, wird die Klasse für jeden Feldwert vor dem Wert geschrieben. Wenn ein unbekanntes Feld auftritt, wird ein Versuch, die Daten zu lesen, durchgeführt. Dies wird verwendet, um die Daten zu überspringen, und wenn Referenzen aktiviert sind, können alle anderen Werte in der Objektgrafik verweisen, dass Daten weiterhin deserialisiert werden können. Wenn das Lesen der Daten fehlschlägt (z. B. die Klasse ist unbekannt oder entfernt), wird eine Ausnahme geworfen oder, wenn chunkedEncoding wahr ist, werden die Daten übersprungen.In beiden Fällen werden Referenzen in den übersprungenen Daten nicht gelesen und die weitere Deserialisierung werden möglicherweise die falschen Referenzen erhalten, wenn die Daten übersprungen sind und Referenzen aktiviert sind. | WAHR |
chunkedEncoding | Wenn es zutrifft, werden Felder mit Chunked -Codierung geschrieben, damit unbekannte Felddaten übersprungen werden können. Dies wirkt sich auf die Leistung aus. | FALSCH |
chunkSize | Die maximale Größe jedes Chunks für die Chunked -Codierung. | 1024 |
Kompatiblerfieldserializer erbt auch alle Einstellungen von FieldSerializer.
BeanSerializer ist FieldSerializer sehr ähnlich, außer dass es eher Bean Getter- und Setter -Methoden als direkten Feldzugriff verwendet. Dies ist etwas langsamer, kann aber sicherer sein, da die öffentliche API zum Konfigurieren des Objekts verwendet wird. Wie FieldSerializer bietet er keine Vorwärts- oder Rückwärtskompatibilität.
CollectionsSerializer serialisiert Objekte, die die Schnittstelle java.util.collection implementieren.
Einstellung | Beschreibung | Standardwert |
---|---|---|
elementsCanBeNull | Wenn falsch ist, wird davon ausgegangen, dass keine Elemente in der Sammlung null sind, die 0-1 Byte pro Element sparen können. | WAHR |
elementClass | Legt die konkrete Klasse für jedes Element in der Sammlung fest. Dies beseitigt die Notwendigkeit, die Klassen -ID für jedes Element zu schreiben. Wenn die Elementklasse (z. B. durch Generika) und eine primitive, primitive Wrapper oder endgültig bekannt ist, schreibt CollectionSerializer die Klassen -ID nicht, selbst wenn diese Einstellung null ist. | null |
elementSerializer | Legt den Serializer für jedes Element in der Sammlung fest. Wenn der Serializer festgelegt ist, forderten einige Serialisierer, dass die Wertklasse ebenfalls festgelegt werden muss. Wenn NULL, wird der bei Kryo für die Klasse eines Elements registrierte Serialisierer verwendet. | null |
Mapserializer serialisiert Objekte, die die Schnittstelle java.util.map implementieren.
Einstellung | Beschreibung | Standardwert |
---|---|---|
keysCanBeNull | Wenn falsch ist, wird davon ausgegangen, dass keine Schlüssel in der Karte null sind, die 0-1 Byte pro Eintrag speichern können. | WAHR |
valuesCanBeNull | Wenn falsch ist, wird davon ausgegangen, dass keine Werte auf der Karte null sind, die 0-1 Byte pro Eintrag speichern können. | WAHR |
keyClass | Legt die Betonklasse für jeden Schlüssel in der Karte fest. Dadurch werden die Klassen -ID für jeden Schlüssel geschrieben. | null |
valueClass | Legt die Betonklasse für jeden Wert in der Karte fest. Dadurch werden die Klassen -ID für jeden Wert geschrieben. | null |
keySerializer | Legt den Serializer für jeden Schlüssel in der Karte fest. Wenn der Wertserializer festgelegt ist, forderten einige Serialisierer, dass die Wertklasse ebenfalls festgelegt werden muss. Wenn NULL, wird der bei Kryo für die Klasse eines Taste registrierte Serializer verwendet. | null |
valueSerializer | Legt den Serializer für jeden Wert in der Karte fest. Wenn der Schlüsselserializer festgelegt ist, forderten einige Serialisierer, dass die Wertklasse ebenfalls festgelegt werden muss. Wenn NULL, wird der bei Kryo für die einzelnen Wert registrierte Serialisierer verwendet. | null |
JavaSerializer und externalizablesserializer sind Kryo-Serialisierer, die die integrierte Serialisierung von Java verwenden. Dies ist so langsam wie üblich Java -Serialisierung, kann aber für Legacy -Klassen notwendig sein.
java.io.externalizable und java.io.Serializable haben standardmäßig keine Standardserialisierer festgelegt, sodass die Standardserialisierer manuell oder die Serialisierer festgelegt werden müssen, wenn die Klasse registriert ist.
class SomeClass implements Externalizable { /* ... */ }
kryo . addDefaultSerializer ( Externalizable . class , ExternalizableSerializer . class );
kryo . register ( SomeClass . class );
kryo . register ( SomeClass . class , new JavaSerializer ());
kryo . register ( SomeClass . class , new ExternalizableSerializer ());
Kryo nutzt die niedrige, leichte Minlog -Protokollierungsbibliothek. Die Protokollierungsstufe kann durch eine der folgenden Methoden festgelegt werden:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo meldet sich bei INFO
(standardmäßig) und über den Ebenen nicht an. DEBUG
ist zweckmäßig während der Entwicklung zu verwenden. TRACE
ist gut zu verwenden, wenn Sie ein bestimmtes Problem debuggen, gibt jedoch im Allgemeinen zu viele Informationen aus, um sie zu überlassen.
Minlog unterstützt eine feste Protokollierungsstufe, wodurch der Java -Compiler zur Kompilierungszeit unter dieser Ebene unterhalb dieses Niveaus entfernt wird. Kryo muss mit einem festen Protokollierungspegel -Minlog -Glas zusammengestellt werden.
Kryo ist nicht fadensicher. Jeder Thread sollte seinen eigenen Kryo-, Eingangs- und Ausgangsinstanzen haben.
Da Kryo nicht sicher ist, ist es relativ teuer, eine Kryo -Instanz zu konstruieren und zu konfigurieren, in einer Multithread -Umgebung, in der ThreadLocal oder das Pooling in Betracht gezogen werden.
static private final ThreadLocal < Kryo > kryos = new ThreadLocal < Kryo >() {
protected Kryo initialValue () {
Kryo kryo = new Kryo ();
// Configure the Kryo instance.
return kryo ;
};
};
Kryo kryo = kryos . get ();
Zum Pooling bietet Kryo die Poolklasse, die Kryo, Eingabe, Ausgabe oder Instanzen einer anderen Klasse bündeln kann.
// Pool constructor arguments: thread safe, soft references, maximum capacity
Pool < Kryo > kryoPool = new Pool < Kryo >( true , false , 8 ) {
protected Kryo create () {
Kryo kryo = new Kryo ();
// Configure the Kryo instance.
return kryo ;
}
};
Kryo kryo = kryoPool . obtain ();
// Use the Kryo instance here.
kryoPool . free ( kryo );
Pool < Output > outputPool = new Pool < Output >( true , false , 16 ) {
protected Output create () {
return new Output ( 1024 , - 1 );
}
};
Output output = outputPool . obtain ();
// Use the Output instance here.
outputPool . free ( output );
Pool < Input > inputPool = new Pool < Input >( true , false , 16 ) {
protected Input create () {
return new Input ( 1024 );
}
};
Input input = inputPool . obtain ();
// Use the Input instance here.
inputPool . free ( input );
Wenn true
als erstes Argument an den Poolkonstruktor übergeben wird, verwendet der Pool die Synchronisation intern und kann gleichzeitig durch mehrere Threads zugegriffen werden.
Wenn true
als zweites Argument an den Poolkonstruktor übergeben wird, speichert der Pool Objekte mit java.lang.ref.softreference. Auf diese Weise können Objekte im Pool Müll erfassen, wenn der Speicherdruck auf das JVM hoch ist. Pool clean
entfernt alle weichen Referenzen, deren Objekt Müll gesammelt wurde. Dies kann die Größe des Pools verringern, wenn keine maximale Kapazität festgelegt wurde. Wenn der Pool eine maximale Kapazität aufweist, ist es nicht erforderlich, clean
zu rufen, da der free
versucht, eine leere Referenz zu entfernen, wenn die maximale Kapazität erreicht wurde.
Der dritte Poolparameter ist die maximale Kapazität. Wenn ein Objekt befreit wird und der Pool bereits die maximale Anzahl freier Objekte enthält, wird das angegebene Objekt zurückgesetzt, aber nicht zum Pool hinzugefügt. Die maximale Kapazität kann ohne Begrenzung weggelassen werden.
Wenn ein Objekt Pool.Poolable implementiert, wird der Poolable reset
aufgerufen, wenn das Objekt befreit wird. Dies gibt dem Objekt die Möglichkeit, in Zukunft seinen Zustand zur Wiederverwendung zurückzusetzen. Alternativ kann der Pool reset
überschrieben werden, um Objekte zurückzusetzen. Eingabe und Ausgabe implementieren Poolable, um ihre position
und total
auf 0 festzulegen. Kryo implementiert nicht poolbar, da sein Objektgraphenzustand normalerweise nach jeder Serialisierung automatisch zurückgesetzt wird (siehe Reset). Wenn Sie das automatische Reset über setAutoReset(false)
deaktivieren, stellen Sie sicher, dass Sie Kryo.reset()
anrufen, bevor Sie die Instanz in den Pool zurückgeben.
Pool getFree
gibt die Anzahl der zur erhaltenen Objekte zurück. Bei Verwendung weicher Referenzen kann diese Zahl Objekte enthalten, die Müll gesammelt wurden. clean
kann zuerst verwendet werden, um leere weiche Referenzen zu entfernen.
Pool getPeak
gibt die höchste Anzahl kostenloser Objekte zurück. Dies kann helfen, festzustellen, ob die maximale Kapazität eines Pools angemessen eingestellt wird. Es kann jederzeit mit resetPeak
zurückgesetzt werden.
Kryo bietet eine Reihe von JMH-basierten Benchmarks und R/GGPLOT2-Dateien.
Kryo kann mit vielen anderen Serialisierungsbibliotheken im JVM -Serialisiererprojekt verglichen werden. Die Benchmarks sind klein, datiert und einheimisch, anstatt JMH zu verwenden, also weniger vertrauenswürdig. Außerdem ist es sehr schwierig, Serialisierungsbibliotheken mit einem Benchmark gründlich zu vergleichen. Bibliotheken haben viele verschiedene Funktionen und haben oft unterschiedliche Ziele, sodass sie möglicherweise völlig unterschiedliche Probleme lösen. Um diese Benchmarks zu verstehen, sollte der Code ausgeführt werden und die serialisierten Daten analysiert und mit Ihren spezifischen Anforderungen kontrastiert werden. Einige Serialisierer sind hoch optimiert und verwenden Codeseiten, andere verwenden nur wenige Zeilen. Dies ist gut zu zeigen, was möglich ist, ist jedoch für viele Situationen möglicherweise kein relevanter Vergleich.
Es gibt eine Reihe von Projekten, die Kryo verwenden. Einige sind unten aufgeführt. Bitte senden Sie eine Pull -Anfrage, wenn Sie Ihr Projekt hier enthalten möchten.