Kryo est un framework de sérialisation de graphes d'objets binaires rapide et efficace pour Java. Les objectifs du projet sont une vitesse élevée, une taille réduite et une API facile à utiliser. Le projet est utile chaque fois que des objets doivent être conservés, que ce soit dans un fichier, une base de données ou sur le réseau.
Kryo peut également effectuer une copie/clonage automatique profond et superficiel. Il s'agit d'une copie directe d'objet en objet, et non d'objet en octets en objet.
Cette documentation concerne Kryo version 5.x. Voir le Wiki pour la version 4.x.
Veuillez utiliser la liste de diffusion Kryo pour les questions, les discussions et l'assistance. Veuillez limiter l'utilisation du système de suivi des problèmes Kryo aux bogues et améliorations, et non aux questions, discussions ou assistance.
Kryo publie deux types d'artefacts/jarres :
Les Kryo JAR sont disponibles sur la page des versions et sur Maven Central. Les derniers instantanés de Kryo, y compris les versions d'instantanés du maître, se trouvent dans le référentiel Sonatype.
Pour utiliser la dernière version de Kryo dans votre application, utilisez cette entrée de dépendance dans votre pom.xml
:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
Pour utiliser la dernière version de Kryo dans une bibliothèque que vous souhaitez publier, utilisez cette entrée de dépendance dans votre pom.xml
:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
Pour utiliser le dernier instantané de Kryo, utilisez :
< 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 >
Tout le monde n’est pas fan de Maven. Utiliser Kryo sans Maven nécessite de placer le Kryo JAR sur votre chemin de classe avec les JAR de dépendance trouvés dans la bibliothèque.
Construire Kryo à partir des sources nécessite JDK11+ et Maven. Pour créer tous les artefacts, exécutez :
mvn clean && mvn install
Aller de l'avant pour montrer comment la bibliothèque peut être utilisée :
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 ;
}
}
La classe Kryo effectue la sérialisation automatiquement. Les classes Output et Input gèrent la mise en mémoire tampon des octets et éventuellement le vidage vers un flux.
Le reste de ce document détaille son fonctionnement et l'utilisation avancée de la bibliothèque.
L'entrée et la sortie des données de Kryo se font à l'aide des classes d'entrée et de sortie. Ces classes ne sont pas thread-safe.
La classe Output est un OutputStream qui écrit des données dans un tampon de tableau d'octets. Ce tampon peut être obtenu et utilisé directement, si un tableau d'octets est souhaité. Si Output reçoit un OutputStream, il videra les octets dans le flux lorsque le tampon sera plein, sinon Output pourra augmenter automatiquement son tampon. La sortie dispose de nombreuses méthodes pour écrire efficacement des primitives et des chaînes en octets. Il fournit des fonctionnalités similaires à DataOutputStream, BufferedOutputStream, FilterOutputStream et ByteArrayOutputStream, le tout dans une seule classe.
Astuce : Output et Input fournissent toutes les fonctionnalités de ByteArrayOutputStream. Il y a rarement une raison pour que la sortie soit envoyée vers un ByteArrayOutputStream.
Output met en mémoire tampon les octets lors de l'écriture dans un OutputStream, donc flush
ou close
doit être appelé une fois l'écriture terminée pour que les octets mis en mémoire tampon soient écrits dans OutputStream. Si le Output n’a pas reçu de OutputStream, l’appel flush
ou close
n’est pas nécessaire. Contrairement à de nombreux flux, une instance de sortie peut être réutilisée en définissant la position ou en définissant un nouveau tableau ou flux d'octets.
Astuce : étant donné que les tampons de sortie sont déjà présents, il n'y a aucune raison que la sortie soit envoyée vers un BufferedOutputStream.
Le constructeur Output à argument zéro crée une sortie non initialisée. La sortie setBuffer
doit être appelée avant que la sortie puisse être utilisée.
La classe Input est un InputStream qui lit les données à partir d'un tampon de tableau d'octets. Ce tampon peut être défini directement si la lecture à partir d'un tableau d'octets est souhaitée. Si l'Input reçoit un InputStream, il remplira le tampon à partir du flux lorsque toutes les données du tampon auront été lues. L'entrée dispose de nombreuses méthodes pour lire efficacement les primitives et les chaînes à partir d'octets. Il fournit des fonctionnalités similaires à DataInputStream, BufferedInputStream, FilterInputStream et ByteArrayInputStream, le tout dans une seule classe.
Astuce : Input fournit toutes les fonctionnalités de ByteArrayInputStream. Il y a rarement une raison pour que l'entrée soit lue à partir d'un ByteArrayInputStream.
Si l'Input close
est appelée, l'InputStream de l'Input est fermé, le cas échéant. Si vous ne lisez pas à partir d’un InputStream, il n’est pas nécessaire d’appeler close
. Contrairement à de nombreux flux, une instance d'entrée peut être réutilisée en définissant la position et la limite, ou en définissant un nouveau tableau d'octets ou InputStream.
Le constructeur d’entrée à argument zéro crée une entrée non initialisée. L’entrée setBuffer
doit être appelée avant que l’entrée puisse être utilisée.
Les classes ByteBufferOutput et ByteBufferInput fonctionnent exactement comme Output et Input, sauf qu'elles utilisent un ByteBuffer plutôt qu'un tableau d'octets.
Les classes UnsafeOutput, UnsafeInput, UnsafeByteBufferOutput et UnsafeByteBufferInput fonctionnent exactement comme leurs homologues non sécurisées, sauf qu'elles utilisent sun.misc.Unsafe pour des performances supérieures dans de nombreux cas. Pour utiliser ces classes, Util.unsafe
doit être vrai.
L'inconvénient de l'utilisation de tampons non sécurisés est que l'endianité native et la représentation des types numériques du système effectuant la sérialisation affectent les données sérialisées. Par exemple, la désérialisation échouera si les données sont écrites sur X86 et lues sur SPARC. De plus, si les données sont écrites avec un tampon non sécurisé, elles doivent être lues avec un tampon non sécurisé.
La plus grande différence de performances avec les tampons non sécurisés concerne les grands tableaux primitifs lorsque le codage à longueur variable n'est pas utilisé. Le codage de longueur variable peut être désactivé pour les tampons non sécurisés ou uniquement pour des champs spécifiques (lors de l'utilisation de FieldSerializer).
Les classes IO fournissent des méthodes pour lire et écrire des valeurs int (varint) et longues (varlong) de longueur variable. Cela se fait en utilisant le 8ème bit de chaque octet pour indiquer si d'autres octets suivent, ce qui signifie qu'un varint utilise 1 à 5 octets et qu'un varlong utilise 1 à 9 octets. L'utilisation d'un codage à longueur variable est plus coûteuse mais rend les données sérialisées beaucoup plus petites.
Lors de l'écriture d'une valeur de longueur variable, la valeur peut être optimisée soit pour des valeurs positives, soit pour des valeurs négatives et positives. Par exemple, lorsqu'il est optimisé pour les valeurs positives, 0 à 127 est écrit sur un octet, 128 à 16 383 sur deux octets, etc. Cependant, les petits nombres négatifs constituent le pire des cas avec 5 octets. Lorsqu’elles ne sont pas optimisées pour le positif, ces plages sont réduites de moitié. Par exemple, -64 à 63 sont écrits sur un octet, 64 à 8191 et -65 à -8192 sur deux octets, etc.
Les tampons d'entrée et de sortie fournissent des méthodes pour lire et écrire des valeurs de taille fixe ou de longueur variable. Il existe également des méthodes permettant au tampon de décider si une valeur de taille fixe ou de longueur variable est écrite. Cela permet au code de sérialisation de garantir que le codage à longueur variable est utilisé pour des valeurs très courantes qui gonfleraient la sortie si une taille fixe était utilisée, tout en permettant à la configuration du tampon de décider de toutes les autres valeurs.
Méthode | Description |
---|---|
écrireInt(int) | Écrit un entier de 4 octets. |
writeVarInt(int, booléen) | Écrit un entier de 1 à 5 octets. |
writeInt(int, booléen) | Écrit un entier de 4 ou 1 à 5 octets (le tampon décide). |
écrireLong(long) | Écrit un message de 8 octets. |
writeVarLong(long, booléen) | Écrit une longueur de 1 à 9 octets. |
writeLong(long, booléen) | Écrit une longueur de 8 ou 1 à 9 octets (le tampon décide). |
Pour désactiver le codage de longueur variable pour toutes les valeurs, les méthodes writeVarInt
, writeVarLong
, readVarInt
et readVarLong
doivent être remplacées.
Il peut être utile d'écrire la longueur de certaines données, puis les données. Lorsque la longueur des données n'est pas connue à l'avance, toutes les données doivent être mises en mémoire tampon pour déterminer leur longueur, puis la longueur peut être écrite, puis les données. l'utilisation d'un seul et grand tampon pour cela empêcherait le streaming et pourrait nécessiter un tampon déraisonnablement grand, ce qui n'est pas idéal.
Le codage en morceaux résout ce problème en utilisant un petit tampon. Lorsque le buffer est plein, sa longueur est écrite, puis les données. Il s’agit d’un morceau de données. Le tampon est vidé et cela continue jusqu'à ce qu'il n'y ait plus de données à écrire. Un morceau d'une longueur nulle indique la fin des morceaux.
Kryo propose des cours pour rendre l'encodage fragmenté facile à utiliser. OutputChunked est utilisé pour écrire des données fragmentées. Il étend Output, ainsi que toutes les méthodes pratiques pour écrire des données. Lorsque le tampon OutputChunked est plein, il vide le morceau vers un autre OutputStream. La méthode endChunk
est utilisée pour marquer la fin d'un ensemble de morceaux.
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 ();
Pour lire les données fragmentées, InputChunked est utilisé. Il étend l'entrée, ainsi que toutes les méthodes pratiques pour lire les données. Lors de la lecture, InputChunked semble atteindre la fin des données lorsqu'il atteint la fin d'un ensemble de morceaux. La méthode nextChunks
passe à l'ensemble de fragments suivant, même si toutes les données n'ont pas été lues à partir de l'ensemble de fragments actuel.
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 ();
Généralement, la sortie et l'entrée offrent de bonnes performances. Les tampons non sécurisés fonctionnent aussi bien, voire mieux, en particulier pour les tableaux primitifs, si leurs incompatibilités multiplateformes sont acceptables. ByteBufferOutput et ByteBufferInput offrent des performances légèrement inférieures, mais cela peut être acceptable si la destination finale des octets doit être un ByteBuffer.
Le codage à longueur variable est plus lent que les valeurs fixes, en particulier lorsque de nombreuses données l'utilisent.
Le codage en morceaux utilise un tampon intermédiaire afin d'ajouter une copie supplémentaire de tous les octets. Cela seul peut être acceptable, mais lorsqu'il est utilisé dans un sérialiseur réentrant, le sérialiseur doit créer un OutputChunked ou un InputChunked pour chaque objet. L'allocation et le garbage collection de ces tampons pendant la sérialisation peuvent avoir un impact négatif sur les performances.
Kryo dispose de trois ensembles de méthodes pour lire et écrire des objets. Si la classe concrète de l'objet n'est pas connue et que l'objet pourrait être nul :
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
Si la classe est connue et que l'objet pourrait être nul :
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
Si la classe est connue et que l'objet ne peut pas être nul :
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
Toutes ces méthodes recherchent d'abord le sérialiseur approprié à utiliser, puis l'utilisent pour sérialiser ou désérialiser l'objet. Les sérialiseurs peuvent appeler ces méthodes pour la sérialisation récursive. Les références multiples au même objet et les références circulaires sont gérées automatiquement par Kryo.
Outre les méthodes de lecture et d'écriture d'objets, la classe Kryo fournit un moyen d'enregistrer les sérialiseurs, lit et écrit efficacement les identifiants de classe, gère les objets nuls pour les sérialiseurs qui ne peuvent pas accepter les valeurs nulles et gère la lecture et l'écriture des références d'objet (si activée). Cela permet aux sérialiseurs de se concentrer sur leurs tâches de sérialisation.
Lors du test et de l'exploration des API Kryo, il peut être utile d'écrire un objet en octets, puis de relire ces octets dans un objet.
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 );
Dans cet exemple, la sortie commence par un tampon d'une capacité de 1 024 octets. Si plus d’octets sont écrits dans la sortie, la taille du tampon augmentera sans limite. Le Output n’a pas besoin d’être fermé car il n’a pas reçu de OutputStream. L'entrée lit directement à partir du tampon byte[]
de la sortie.
Kryo prend en charge la création de copies profondes et superficielles d'objets en utilisant l'affectation directe d'un objet à un autre. C'est plus efficace que la sérialisation en octets et retour aux objets.
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
Tous les sérialiseurs utilisés doivent prendre en charge la copie. Tous les sérialiseurs fournis avec Kryo prennent en charge la copie.
Comme pour la sérialisation, lors de la copie, plusieurs références au même objet et les références circulaires sont gérées automatiquement par Kryo si les références sont activées.
Si vous utilisez Kryo uniquement pour la copie, l'enregistrement peut être désactivé en toute sécurité.
Kryo getOriginalToCopyMap
peut être utilisé après la copie d'un graphique d'objet pour obtenir une carte des anciens objets vers les nouveaux. La carte est effacée automatiquement par Kryo reset
, elle n'est donc utile que lorsque Kryo setAutoReset
est faux.
Par défaut, les références ne sont pas activées. Cela signifie que si un objet apparaît plusieurs fois dans un graphique d'objets, il sera écrit plusieurs fois et sera désérialisé en tant qu'objets multiples et différents. Lorsque les références sont désactivées, les références circulaires entraîneront l’échec de la sérialisation. Les références sont activées ou désactivées avec Kryo setReferences
pour la sérialisation et setCopyReferences
pour la copie.
Lorsque les références sont activées, une variante est écrite avant chaque objet la première fois qu'il apparaît dans le graphique d'objets. Pour les apparitions ultérieures de cette classe dans le même graphe d'objets, seule une variante est écrite. Après la désérialisation, les références d'objet sont restaurées, y compris les références circulaires. Les sérialiseurs utilisés doivent prendre en charge les références en appelant reference
Kryo dans Serializer read
.
L'activation des références a un impact sur les performances, car chaque objet lu ou écrit doit être suivi.
Sous les couvertures, un ReferenceResolver gère le suivi des objets qui ont été lus ou écrits et fournit des ID de référence int. Plusieurs implémentations sont fournies :
ReferenceResolver useReferences(Class)
peut être remplacé. Il renvoie un booléen pour décider si les références sont prises en charge pour une classe. Si une classe ne prend pas en charge les références, l'ID de référence varint n'est pas écrit avant les objets de ce type. Si une classe n'a pas besoin de références et que des objets de ce type apparaissent plusieurs fois dans le graphique d'objets, la taille sérialisée peut être considérablement réduite en désactivant les références pour cette classe. Le résolveur de référence par défaut renvoie false pour tous les wrappers et énumérations primitives. Il est courant de renvoyer également false pour String et d'autres classes, en fonction des graphiques d'objets sérialisés.
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
Le résolveur de références détermine le nombre maximum de références dans un seul graphe d'objet. Les indices de tableau Java sont limités à Integer.MAX_VALUE
, donc les résolveurs de référence qui utilisent des structures de données basées sur des tableaux peuvent entraîner une java.lang.NegativeArraySizeException
lors de la sérialisation de plus d'environ 2 milliards d'objets. Kryo utilise des identifiants de classe int, de sorte que le nombre maximum de références dans un seul graphique d'objet est limité à la gamme complète de nombres positifs et négatifs dans un int (~ 4 milliards).
Kryo getContext
renvoie une carte pour stocker les données utilisateur. L'instance Kryo est disponible pour tous les sérialiseurs, ces données sont donc facilement accessibles à tous les sérialiseurs.
Kryo getGraphContext
est similaire, mais est effacé après la sérialisation ou la désérialisation de chaque graphique d'objet. Cela facilite la gestion des états qui ne concernent que le graphe d'objets actuel. Par exemple, cela peut être utilisé pour écrire des données de schéma la première fois qu'une classe est rencontrée dans un graphe d'objets. Voir CompatibleFieldSerializer pour un exemple.
Par défaut, Kryo reset
est appelé une fois que chaque graphique d'objet entier est sérialisé. Cela réinitialise les noms de classe non enregistrés dans le résolveur de classe, les références aux objets précédemment sérialisés ou désérialisés dans le résolveur de référence et efface le contexte du graphique. Kryo setAutoReset(false)
peut être utilisé pour désactiver automatiquement reset
des appels, permettant à cet état de s'étendre sur plusieurs graphiques d'objets.
Kryo est un framework pour faciliter la sérialisation. Le framework lui-même n'applique pas de schéma et ne se soucie pas de savoir quoi ou comment les données sont écrites ou lues. Les sérialiseurs sont enfichables et prennent les décisions sur ce qu'il faut lire et écrire. De nombreux sérialiseurs sont fournis prêts à l'emploi pour lire et écrire des données de différentes manières. Bien que les sérialiseurs fournis puissent lire et écrire la plupart des objets, ils peuvent facilement être remplacés partiellement ou complètement par vos propres sérialiseurs.
Lorsque Kryo va écrire une instance d'un objet, il devra peut-être d'abord écrire quelque chose qui identifie la classe de l'objet. Par défaut, toutes les classes que Kryo lira ou écrira doivent être enregistrées au préalable. L'enregistrement fournit un ID de classe int, le sérialiseur à utiliser pour la classe et l'instanciateur d'objet utilisé pour créer des instances de la classe.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Lors de la désérialisation, les classes enregistrées doivent avoir exactement les mêmes identifiants que lors de la sérialisation. Une fois enregistrée, une classe se voit attribuer le prochain ID entier disponible, le plus bas, ce qui signifie que l'ordre des classes enregistrées est important. L'ID de classe peut éventuellement être spécifié explicitement pour rendre l'ordre sans importance :
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
Les ID de classe -1 et -2 sont réservés. Les ID de classe 0 à 8 sont utilisés par défaut pour les types primitifs et String, bien que ces ID puissent être réutilisés. Les identifiants sont écrits sous forme de variantes optimisées positives, ils sont donc plus efficaces lorsqu'il s'agit de petits entiers positifs. Les identifiants négatifs ne sont pas sérialisés efficacement.
Sous les couvertures, un ClassResolver gère réellement la lecture et l'écriture d'octets pour représenter une classe. L'implémentation par défaut est suffisante dans la plupart des cas, mais elle peut être remplacée pour personnaliser ce qui se passe lorsqu'une classe est enregistrée, ce qui se passe lorsqu'une classe non enregistrée est rencontrée lors de la sérialisation et ce qui est lu et écrit pour représenter une classe.
Kryo peut être configuré pour permettre la sérialisation sans enregistrer les classes à l'avance.
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
L'utilisation des cours inscrits et non inscrits peut être mixte. Les cours non inscrits présentent deux inconvénients majeurs :
Si vous utilisez Kryo uniquement pour la copie, l'enregistrement peut être désactivé en toute sécurité.
Lorsque l'enregistrement n'est pas requis, Kryo setWarnUnregisteredClasses
peut être activé pour enregistrer un message lorsqu'une classe non enregistrée est rencontrée. Cela peut être utilisé pour obtenir facilement une liste de toutes les classes non enregistrées. Kryo unregisteredClassMessage
peut être remplacé pour personnaliser le message du journal ou entreprendre d'autres actions.
Lorsqu'une classe est enregistrée, une instance de sérialiseur peut éventuellement être spécifiée. Lors de la désérialisation, les classes enregistrées doivent avoir exactement les mêmes sérialiseurs et configurations de sérialiseur que lors de la sérialisation.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
Si aucun sérialiseur n'est spécifié ou lorsqu'une classe non enregistrée est rencontrée, un sérialiseur est choisi automatiquement dans une liste de « sérialiseurs par défaut » qui mappe une classe à un sérialiseur. Le fait d'avoir de nombreux sérialiseurs par défaut n'affecte pas les performances de sérialisation, donc par défaut, Kryo dispose de plus de 50 sérialiseurs par défaut pour diverses classes JRE. Des sérialiseurs par défaut supplémentaires peuvent être ajoutés :
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Cela entraînera la création d’une instance SomeSerializer lorsque SomeClass ou toute classe qui étend ou implémente SomeClass est enregistrée.
Les sérialiseurs par défaut sont triés de manière à ce que les classes plus spécifiques soient mises en correspondance en premier, mais sinon elles sont mises en correspondance dans l'ordre dans lequel elles sont ajoutées. L’ordre dans lequel ils sont ajoutés peut être pertinent pour les interfaces.
Si aucun sérialiseur par défaut ne correspond à une classe, le sérialiseur global par défaut est utilisé. Le sérialiseur global par défaut est défini par défaut sur FieldSerializer, mais peut être modifié. Habituellement, le sérialiseur global peut gérer de nombreux types différents.
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
Avec ce code, en supposant qu'aucun sérialiseur par défaut ne corresponde à SomeClass, TaggedFieldSerializer sera utilisé.
Une classe peut également utiliser l'annotation DefaultSerializer, qui sera utilisée à la place de choisir l'un des sérialiseurs par défaut de Kryo :
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
Pour une flexibilité maximale, Kryo getDefaultSerializer
peut être remplacé pour implémenter une logique personnalisée pour choisir et instancier un sérialiseur.
La méthode addDefaultSerializer(Class, Class)
ne permet pas la configuration du sérialiseur. Une usine de sérialiseur peut être définie à la place d'une classe de sérialiseur, permettant à l'usine de créer et de configurer chaque instance de sérialiseur. Des usines sont fournies pour les sérialiseurs courants, souvent avec une méthode getConfig
pour configurer les sérialiseurs créés.
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 );
La fabrique de sérialiseurs possède une méthode isSupported(Class)
qui lui permet de refuser de gérer une classe, même si elle correspond par ailleurs à la classe. Cela permet à une usine de vérifier plusieurs interfaces ou d'implémenter une autre logique.
Alors que certains sérialiseurs sont destinés à une classe spécifique, d'autres peuvent sérialiser de nombreuses classes différentes. Les sérialiseurs peuvent utiliser Kryo newInstance(Class)
pour créer une instance de n'importe quelle classe. Cela se fait en recherchant l'inscription pour la classe, puis en utilisant l'ObjectInstantiator de l'inscription. L'instanciateur peut être précisé lors de l'inscription.
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Si l'enregistrement n'a pas d'instanciateur, un est fourni par Kryo newInstantiator
. Pour personnaliser la façon dont les objets sont créés, Kryo newInstantiator
peut être remplacé ou une InstantiatorStrategy fournie.
Kryo fournit DefaultInstantiatorStrategy qui crée des objets à l'aide de ReflectASM pour appeler un constructeur sans argument. Si cela n'est pas possible, il utilise la réflexion pour appeler un constructeur sans argument. Si cela échoue également, il lève une exception ou tente une InstantiatorStrategy de secours. Reflection utilise setAccessible
, donc un constructeur privé sans argument peut être un bon moyen de permettre à Kryo de créer des instances d'une classe sans affecter l'API publique.
DefaultInstantiatorStrategy est la méthode recommandée pour créer des objets avec Kryo. Il exécute les constructeurs comme cela serait le cas avec le code Java. Des mécanismes alternatifs et extralinguistiques peuvent également être utilisés pour créer des objets. L'Objenesis StdInstantiatorStrategy utilise des API spécifiques à la JVM pour créer une instance d'une classe sans appeler aucun constructeur. Utiliser ceci est dangereux car la plupart des classes s'attendent à ce que leurs constructeurs soient appelés. La création de l'objet en contournant ses constructeurs peut laisser l'objet dans un état non initialisé ou invalide. Les classes doivent être conçues pour être créées de cette manière.
Kryo peut être configuré pour essayer DefaultInstantiatorStrategy d'abord, puis revenir à StdInstantiatorStrategy si nécessaire.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
Une autre option est SerializingInstantiatorStrategy, qui utilise le mécanisme de sérialisation intégré de Java pour créer une instance. En utilisant cela, la classe doit implémenter java.io.Seriallessly et le premier constructeur à argument zéro dans une super classe est invoqué. Cela contourne également les constructeurs et est donc dangereux pour les mêmes raisons que StdInstantiatorStrategy.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
Alternativement, certains sérialiseurs génériques fournissent des méthodes qui peuvent être remplacées pour personnaliser la création d'objets pour un type spécifique, au lieu d'appeler Kryo newInstance
.
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 );
}
});
Certains sérialiseurs fournissent une méthode writeHeader
qui peut être remplacée pour écrire les données nécessaires à create
au bon moment.
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 ));
}
}
Si un sérialiseur ne fournit pas writeHeader
, l'écriture des données pour create
peut être effectuée en write
.
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 ());
}
}
Même lorsqu'un sérialiseur connaît la classe attendue pour une valeur (par exemple la classe d'un champ), si la classe concrète de la valeur n'est pas définitive, le sérialiseur doit d'abord écrire l'ID de classe, puis la valeur. Les classes finales peuvent être sérialisées plus efficacement car elles ne sont pas polymorphes.
Kryo isFinal
est utilisé pour déterminer si une classe est finale. Cette méthode peut être remplacée pour renvoyer true même pour les types qui ne sont pas définitifs. Par exemple, si une application utilise ArrayList de manière intensive mais n'utilise jamais de sous-classe ArrayList, traiter ArrayList comme final pourrait permettre à FieldSerializer d'économiser 1 à 2 octets par champ ArrayList.
Kryo peut sérialiser les fermetures Java 8+ qui implémentent java.io.Seriallessly, avec quelques mises en garde. Les fermetures sérialisées sur une JVM peuvent ne pas réussir à être désérialisées sur une autre JVM.
Kryo isClosure
est utilisé pour déterminer si une classe est une fermeture. Si tel est le cas, ClosureSerializer.Closure est utilisé pour rechercher l'enregistrement de la classe au lieu de la classe de fermeture. Pour sérialiser les fermetures, les classes suivantes doivent être enregistrées : ClosureSerializer.Closure, Object[] et Class. De plus, la classe de capture de la fermeture doit être enregistrée.
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 );
La sérialisation des fermetures qui n'implémentent pas Serialisable est possible avec un certain effort.
Kryo prend en charge les flux, il est donc trivial d'utiliser la compression ou le chiffrement sur tous les octets sérialisés :
OutputStream outputStream = new DeflaterOutputStream ( new FileOutputStream ( "file.bin" ));
Output output = new Output ( outputStream );
Kryo kryo = new Kryo ();
kryo . writeObject ( output , object );
output . close ();
Si nécessaire, un sérialiseur peut être utilisé pour compresser ou chiffrer les octets uniquement pour un sous-ensemble d’octets d’un graphe d’objets. Par exemple, consultez DeflateSerializer ou BlowfishSerializer. Ces sérialiseurs enveloppent un autre sérialiseur pour coder et décoder les octets.
La classe abstraite Serializer définit des méthodes pour passer des objets aux octets et des octets aux objets.
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 n'a que deux méthodes qui doivent être implémentées. write
écrit l'objet sous forme d'octets dans la sortie. read
crée une nouvelle instance de l'objet et lit à partir de l'entrée pour la remplir.
Lorsque Kryo est utilisé pour lire un objet imbriqué dans Serializer read
, reference
Kryo doit d'abord être appelée avec l'objet parent s'il est possible pour l'objet imbriqué de référencer l'objet parent. Il n'est pas nécessaire d'appeler reference
Kryo si les objets imbriqués ne peuvent pas référencer l'objet parent, si Kryo n'est pas utilisé pour les objets imbriqués ou si les références ne sont pas utilisées. Si les objets imbriqués peuvent utiliser le même sérialiseur, celui-ci doit être réentrant.
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 ;
}
Les sérialiseurs ne doivent généralement pas utiliser directement d'autres sérialiseurs, mais les méthodes de lecture et d'écriture Kryo doivent être utilisées. Cela permet à Kryo d'orchestrer la sérialisation et de gérer des fonctionnalités telles que les références et les objets nuls. Parfois, un sérialiseur sait quel sérialiseur utiliser pour un objet imbriqué. Dans ce cas, il doit utiliser les méthodes de lecture et d'écriture de Kryo qui acceptent un sérialiseur.
Si l'objet peut être nul :
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
Si l'objet ne peut pas être nul :
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
Pendant la sérialisation, Kryo getDepth
fournit la profondeur actuelle du graphe d'objets.
Lorsqu'une sérialisation échoue, une KryoException peut être levée avec des informations de trace de sérialisation sur l'endroit où l'exception s'est produite dans le graphe d'objets. Lors de l'utilisation de sérialiseurs imbriqués, KryoException peut être interceptée pour ajouter des informations de trace de sérialisation.
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 ;
}
}
Les sérialiseurs fournis par Kryo utilisent la pile d'appels lors de la sérialisation d'objets imbriqués. Kryo minimise les appels de pile, mais un débordement de pile peut se produire pour les graphes d'objets extrêmement profonds. Il s'agit d'un problème courant pour la plupart des bibliothèques de sérialisation, y compris la sérialisation Java intégrée. La taille de la pile peut être augmentée en utilisant -Xss
, mais notez que cela s'applique à tous les threads. Les grandes tailles de pile dans une JVM avec de nombreux threads peuvent utiliser une grande quantité de mémoire.
Kryo setMaxDepth
peut être utilisé pour limiter la profondeur maximale d'un graphique d'objet. Cela peut empêcher les données malveillantes de provoquer un débordement de pile.
Par défaut, les sérialiseurs ne recevront jamais de valeur nulle, mais Kryo écrira un octet si nécessaire pour indiquer null ou non null. Si un sérialiseur peut être plus efficace en gérant lui-même les valeurs NULL, il peut appeler Serializer setAcceptsNull(true)
. Cela peut également être utilisé pour éviter d'écrire l'octet indiquant la valeur nulle lorsqu'on sait que toutes les instances que le sérialiseur gérera ne seront jamais nulles.
Kryo getGenerics
fournit des informations de type générique afin que les sérialiseurs puissent être plus efficaces. Ceci est le plus souvent utilisé pour éviter d'écrire la classe lorsque la classe de paramètre de type est finale.
L'inférence de type générique est activée par défaut et peut être désactivée avec Kryo setOptimizedGenerics(false)
. La désactivation de l'optimisation des génériques peut augmenter les performances au prix d'une taille sérialisée plus grande.
Si la classe a un seul paramètre de type, nextGenericClass
renvoie la classe de paramètre de type, ou null s'il n'y en a pas. Après avoir lu ou écrit des objets imbriqués, popGenericType
doit être appelé. Voir CollectionSerializer pour un exemple.
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 ;
}
}
Pour une classe avec plusieurs paramètres de type, nextGenericTypes
renvoie un tableau d'instances GenericType et resolve
est utilisée pour obtenir la classe pour chaque GenericType. Après avoir lu ou écrit des objets imbriqués, popGenericType
doit être appelé. Voir MapSerializer pour un exemple.
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 ;
}
}
Pour les sérialiseurs qui transmettent des informations sur les paramètres de type pour les objets imbriqués dans le graphe d'objets (utilisation quelque peu avancée), GenericsHierarchy est d'abord utilisé pour stocker les paramètres de type d'une classe. Pendant la sérialisation, Generics pushTypeVariables
est appelé avant que les types génériques ne soient résolus (le cas échéant). Si >0 est renvoyé, cela doit être suivi de Generics popTypeVariables
. Voir FieldSerializer pour un exemple.
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 ;
}
}
Au lieu d'utiliser un sérialiseur, une classe peut choisir de faire sa propre sérialisation en implémentant KryoSeriallessly (similaire à java.io.Externallessly).
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 );
}
}
Évidemment, l'instance doit déjà être créée avant que read
puisse être appelé, la classe ne peut donc pas contrôler sa propre création. Une classe KryoSerializing utilisera le sérialiseur par défaut KryoSeriallesslySerializer, qui utilise Kryo newInstance
pour créer une nouvelle instance. Il est trivial d'écrire votre propre sérialiseur pour personnaliser le processus, appeler des méthodes avant ou après la sérialisation, etc.
Les sérialiseurs ne prennent en charge la copie que si copy
est remplacée. Semblable à Serializer read
, cette méthode contient la logique pour créer et configurer la copie. Tout comme read
, reference
Kryo doit être appelée avant que Kryo ne soit utilisée pour copier des objets enfants, si l'un des objets enfants peut faire référence à l'objet parent.
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 ;
}
}
Au lieu d'utiliser un sérialiseur, les classes peuvent implémenter KryoCopyable pour effectuer leur propre copie :
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 ;
}
}
Le sérialiseur setImmutable(true)
peut être utilisé lorsque le type est immuable. Dans ce cas, copy
du Serializer n'a pas besoin d'être implémentée : l'implémentation copy
par défaut renverra l'objet d'origine.
Les règles empiriques suivantes sont appliquées à la numérotation des versions de Kryo :
La mise à niveau de toute dépendance est un événement important, mais une bibliothèque de sérialisation est plus sujette à la rupture que la plupart des dépendances. Lors de la mise à niveau de Kryo, vérifiez les différences de version et testez la nouvelle version approfondie dans vos propres applications. Nous essayons de le rendre aussi sûr et facile que possible.
Les sérialiseurs Kryo fournis par défaut supposent que Java sera utilisé pour la désérialisation, de sorte qu'ils ne définissent pas explicitement le format écrit. Les sérialiseurs pourraient être écrits à l'aide d'un format standardisé qui est plus facilement lu par d'autres langues, mais cela n'est pas fourni par défaut.
Pour certains besoins, tels que le stockage à long terme des octets sérialisés, il peut être important de savoir comment la sérialisation gère les changements dans les classes. Ceci est connu sous le nom de compatibilité directe (octets de lecture sérialisés par les classes plus récentes) et de compatibilité arrière (octets de lecture sérialisés par des classes plus anciennes). Kryo fournit quelques sérialiseurs génériques qui adoptent différentes approches pour gérer la compatibilité. Des sérialiseurs supplémentaires peuvent facilement être développés pour une compatibilité avant et arrière, comme un sérialiseur qui utilise un schéma externe écrit à la main.
Lorsqu'une classe change plus que son sérialiseur peut gérer, un sérialiseur peut être écrit pour transférer les données dans une classe différente. Toute l'utilisation de l'ancienne classe dans le code d'application doit être remplacée par la nouvelle classe. L'ancienne classe est conservée uniquement pour ce sérialiseur.
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 fournit à de nombreux sérialiseurs avec diverses options de configuration et niveaux de compatibilité. Des sérialiseurs supplémentaires peuvent être trouvés dans le projet sœur de Kryo-Serializers, qui héberge des sérialiseurs qui accèdent aux API privés ou qui ne sont pas autrement parfaitement en sécurité sur tous les JVM. Plus de sérialiseurs peuvent être trouvés dans la section des liens.
FieldSerializer fonctionne en sérialisant chaque champ non transitoire. Il peut sérialiser les POJOS et de nombreuses autres classes sans aucune configuration. Tous les champs non publics sont écrits et lus par défaut, il est donc important d'évaluer chaque classe qui sera sérialisée. Si les champs sont publics, la sérialisation peut être plus rapide.
FieldSerializer est efficace en n'écrivant que les données sur le terrain, sans aucune information de schéma, en utilisant les fichiers de classe Java comme schéma. Il ne prend pas en charge l'ajout, la suppression ou la modification du type de champs sans invalider les octets précédemment sérialisés. Les champs de changement de nom ne sont autorisés que s'il ne modifie pas l'ordre alphabétique des champs.
Les inconvénients de compatibilité de FieldSerializer peuvent être acceptables dans de nombreuses situations, par exemple lors de l'envoi de données sur un réseau, mais peuvent ne pas être un bon choix pour le stockage de données à long terme car les classes Java ne peuvent pas évoluer. Dans de nombreux cas, TaggedFieldSerializer est un meilleur choix.
Paramètre | Description | Valeur par défaut |
---|---|---|
fieldsCanBeNull | En cas de faux, on suppose qu'aucune valeur de champ n'est nulle, ce qui peut économiser 0-1 octet par champ. | vrai |
setFieldsAsAccessible | Lorsqu'il est vrai, tous les champs non transitoires (y compris les champs privés) seront sérialisés et setAccessible si nécessaire. S'il est faux, seuls les champs de l'API public seront sérialisés. | vrai |
ignoreSyntheticFields | Si cela est vrai, les champs synthétiques (générés par le compilateur pour la portée) sont sérialisés. | FAUX |
fixedFieldTypes | Si c'est vrai, il est supposé que le type de béton de chaque valeur de champ correspond au type du champ. Cela supprime la nécessité d'écrire l'ID de classe pour les valeurs de champ. | FAUX |
copyTransient | Si cela est vrai, tous les champs transitoires seront copiés. | vrai |
serializeTransient | Si cela est vrai, les champs transitoires seront sérialisés. | FAUX |
variableLengthEncoding | Si cela est vrai, les valeurs de longueur variable sont utilisées pour les champs int et longs. | vrai |
extendedFieldNames | Si cela est vrai, les noms de champ sont préfixés par leur classe de déclaration. Cela peut éviter les conflits lorsqu'une sous-classe a un champ avec le même nom qu'une super classe. | FAUX |
FieldSerializer fournit les champs qui seront sérialisés. Les champs peuvent être supprimés, ils ne seront donc pas sérialisés. Les champs peuvent être configurés pour rendre la série plus efficace.
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 ());
Paramètre | Description | Valeur par défaut |
---|---|---|
canBeNull | Lorsqu'il est faux, il est supposé que la valeur du champ n'est jamais nul, ce qui peut enregistrer 0-1 octet. | vrai |
valueClass | Définit la classe de béton et le sérialiseur à utiliser pour la valeur du champ. Cela supprime la nécessité d'écrire l'ID de classe pour la valeur. Si la classe de la valeur du champ est un wrapper primitif et primitif ou final, ce paramètre par défaut la classe du champ. | nul |
serializer | Définit le sérialiseur à utiliser pour la valeur du champ. Si le sérialiseur est défini, certains sérialiseurs exigeaient également que la classe de valeur soit également définie. Si NULL, le sérialiseur enregistré auprès de Kryo pour la classe de la valeur de champ sera utilisé. | nul |
variableLengthEncoding | Si cela est vrai, des valeurs de longueur variable sont utilisées. Cela ne s'applique qu'aux champs int ou longs. | vrai |
optimizePositive | Si vrai, les valeurs positives sont optimisées pour les valeurs de longueur variable. Cela ne s'applique qu'aux champs int ou longs lorsque le codage de longueur variable est utilisé. | vrai |
Les annotations peuvent être utilisées pour configurer les sérialiseurs pour chaque champ.
Annotation | Description |
---|---|
@Bind | Définit les paramètres CachedField pour n'importe quel champ. |
@CollectionBind | Définit les paramètres de collectionRerializer pour les champs de collecte. |
@MapBind | Définit les paramètres de MapSerializer pour les champs de carte. |
@NotNull | Marque un champ comme n'étant jamais nul. |
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 étend FieldSerializer et offre une compatibilité vers l'arrière. Cela signifie que des champs peuvent être ajoutés sans invalider les octets précédemment sérialisés. La suppression, le changement de nom ou la modification du type de champ n'est pas prise en charge.
Lorsqu'un champ est ajouté, il doit avoir l'annotation @Since(int)
pour indiquer la version qu'il a été ajouté afin d'être compatible avec les octets précédemment sérialisés. La valeur d'annotation ne doit jamais changer.
Version FieldSerializer ajoute très peu de frais généraux à FieldSerializer: une seule variante supplémentaire.
Paramètre | Description | Valeur par défaut |
---|---|---|
compatible | Lorsqu'il est faux, une exception est lancée lors de la lecture d'un objet avec une version différente. La version d'un objet est la version maximale de n'importe quel champ. | vrai |
VersionFieldSerializer hérite également de tous les paramètres de FieldSerializer.
TaggedFieldSerializer étend FieldSerializer pour fournir une compatibilité vers l'arrière et une compatibilité directe en option. Cela signifie que des champs peuvent être ajoutés ou renommés et éventuellement supprimés sans invalider les octets précédemment sérialisés. La modification du type de champ n'est pas prise en charge.
Seuls les champs qui ont une annotation @Tag(int)
sont sérialisés. Les valeurs de balise de champ doivent être uniques, à la fois dans une classe et toutes ses super classes. Une exception est lancée si des valeurs de balise en double sont rencontrées.
Les performances de compatibilité et de sérialisation vers l'avant et vers l'arrière dépend des paramètres readUnknownTagData
et chunkedEncoding
. De plus, une variante est écrite avant chaque champ pour la valeur de balise.
Lorsque readUnknownTagData
et chunkedEncoding
sont faux, les champs ne doivent pas être supprimés mais l'annotation @Deprecated
peut être appliquée. Les champs obsolètes sont lus lors de la lecture d'anciens octets mais ne sont pas écrits à de nouveaux octets. Les classes peuvent évoluer en lisant les valeurs des champs obsolètes et en les écrivant ailleurs. Les champs peuvent être renommés et / ou rendus privés pour réduire l'encombrement dans la classe (par exemple, ignored1
, ignored2
).
TaggedFieldSerializer (avec readUnknownTagData
et chunkedEncoding
false) est le sérialiseur suggéré pour la plupart des classes où les champs peuvent être annotés. Il permet à l'évolution des classes et des champs d'être supprimés des données sérialisées (via la dépréciation), répondant aux besoins de la plupart des applications sans ajouter beaucoup à la taille sérialisée.
Paramètre | Description | Valeur par défaut |
---|---|---|
readUnknownTagData | Lorsque FALSE et une balise inconnue sont rencontrées, une exception est lancée ou, si chunkedEncoding est vrai, les données sont ignorées.Lorsque cela est vrai, la classe pour chaque valeur de champ est écrite avant la valeur. Lorsqu'une balise inconnue est rencontrée, une tentative de lire les données est faite. Ceci est utilisé pour sauter les données et, si les références sont activées, toutes les autres valeurs du graphique d'objet faisant référence à ce que les données peuvent toujours être désérialisées. Si la lecture des données échoue (par exemple, la classe est inconnue ou a été supprimée), une exception est lancée ou, si chunkedEncoding est vrai, les données sont ignorées.Dans les deux cas, si les données sont ignorées et que les références sont activées, les références dans les données sautées ne sont pas lues et la désérialisation supplémentaire peut recevoir les mauvaises références et échouer. | FAUX |
chunkedEncoding | Lorsque cela est vrai, les champs sont écrits avec un codage en morceaux pour permettre à des données de champ inconnues. Cela a un impact sur les performances. | FAUX |
chunkSize | La taille maximale de chaque morceau pour le codage en morceaux. | 1024 |
TaggedFieldSerializer hérite également de tous les paramètres de FieldSerializer.
CompatibleFieldSerializer étend les champs de champs pour fournir une compatibilité à la fois vers l'avant et vers l'arrière. Cela signifie que des champs peuvent être ajoutés ou supprimés sans invalider les octets précédemment sérialisés. Le changement de nom ou la modification du type de champ n'est pas pris en charge. Comme FieldSerializer, il peut sérialiser la plupart des classes sans avoir besoin d'annotations.
Les performances de compatibilité et de sérialisation vers l'avant et vers l'arrière dépend des paramètres readUnknownFieldData
et chunkedEncoding
. De plus, la première fois que la classe est rencontrée dans les octets sérialisés, un schéma simple est écrit contenant les chaînes de nom de champ. Étant donné que les données sur le terrain sont identifiées par nom, si une super classe a un champ avec le même nom qu'une sous-classe, extendedFieldNames
doivent être vrais.
Paramètre | Description | Valeur par défaut |
---|---|---|
readUnknownFieldData | Lorsqu'une fausse et un champ inconnu sont rencontrés, une exception est lancée ou, si chunkedEncoding est vrai, les données sont ignorées.Lorsque cela est vrai, la classe pour chaque valeur de champ est écrite avant la valeur. Lorsqu'un champ inconnu est rencontré, une tentative de lire les données est faite. Ceci est utilisé pour sauter les données et, si les références sont activées, toutes les autres valeurs du graphique d'objet faisant référence à ce que les données peuvent toujours être désérialisées. Si la lecture des données échoue (par exemple, la classe est inconnue ou a été supprimée), une exception est lancée ou, si chunkedEncoding est vrai, les données sont ignorées.Dans les deux cas, si les données sont ignorées et que les références sont activées, les références dans les données sautées ne sont pas lues et la désérialisation supplémentaire peut recevoir les mauvaises références et échouer. | vrai |
chunkedEncoding | Lorsque cela est vrai, les champs sont écrits avec un codage en morceaux pour permettre à des données de champ inconnues. Cela a un impact sur les performances. | FAUX |
chunkSize | La taille maximale de chaque morceau pour le codage en morceaux. | 1024 |
CompatibleFieldSerializer hérite également de tous les paramètres de FieldSerializer.
Le haricot est très similaire à FieldSerializer, sauf qu'il utilise des méthodes de bean Getter et Setter plutôt que d'accès direct sur le terrain. Cela est un peu plus lent, mais peut être plus sûr car il utilise l'API publique pour configurer l'objet. Comme FieldSerializer, il ne fournit aucune compatibilité avant ou arrière.
CollectionSerializer sérialise des objets qui implémentent l'interface java.util.collection.
Paramètre | Description | Valeur par défaut |
---|---|---|
elementsCanBeNull | En cas de faux, on suppose qu'aucun éléments de la collection n'est nul, ce qui peut économiser 0-1 octet par élément. | vrai |
elementClass | Définit la classe de béton à utiliser pour chaque élément de la collection. Cela supprime la nécessité d'écrire l'ID de classe pour chaque élément. Si la classe d'éléments est connue (par exemple par le biais de génériques) et un wrapper primitif et primitif ou final, collectionserializer n'écrit pas l'ID de classe même lorsque ce paramètre est nul. | nul |
elementSerializer | Définit le sérialiseur à utiliser pour chaque élément de la collection. Si le sérialiseur est défini, certains sérialiseurs exigeaient également que la classe de valeur soit également définie. Si NULL, le sérialiseur enregistré auprès de Kryo pour la classe de chaque élément sera utilisé. | nul |
MapSerializer sérialise des objets qui implémentent l'interface java.util.map.
Paramètre | Description | Valeur par défaut |
---|---|---|
keysCanBeNull | En cas de faux, il est supposé qu'aucune touche dans la carte n'est nulle, ce qui peut économiser 0-1 octet par entrée. | vrai |
valuesCanBeNull | En cas de faux, on suppose qu'aucune valeur dans la carte n'est nulle, ce qui peut économiser 0-1 octet par entrée. | vrai |
keyClass | Définit la classe de béton à utiliser pour chaque clé de la carte. Cela supprime la nécessité d'écrire l'ID de classe pour chaque clé. | nul |
valueClass | Définit la classe de béton à utiliser pour chaque valeur de la carte. Cela supprime la nécessité d'écrire l'ID de classe pour chaque valeur. | nul |
keySerializer | Définit le sérialiseur à utiliser pour chaque clé de la carte. Si le sérialiseur de valeur est défini, certains sérialiseurs exigeaient que la classe de valeur soit également définie. Si NULL, le sérialiseur enregistré auprès de Kryo pour la classe de chaque clé sera utilisé. | nul |
valueSerializer | Définit le sérialiseur à utiliser pour chaque valeur de la carte. Si le sérialiseur clé est défini, certains sérialiseurs exigeaient que la classe de valeur soit également définie. Si NULL, le sérialiseur enregistré auprès de Kryo pour la classe de chaque valeur sera utilisé. | nul |
Javaserializer et ExternalizableSerializer sont des sérialiseurs Kryo qui utilisent la sérialisation intégrée de Java. C'est aussi lent que la sérialisation Java d'habitude, mais peut être nécessaire pour les classes héritées.
java.io.externalizable et java.io.serializable n'ont pas défini de sérialiseurs par défaut par défaut, de sorte que les sérialiseurs par défaut doivent être définis manuellement ou les sérialiseurs définis lorsque la classe est enregistrée.
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 utilise la bibliothèque de journalisation Minlog légère et légère. Le niveau de journalisation peut être fixé par l'une des méthodes suivantes:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo ne fait pas de journalisation aux niveaux INFO
(par défaut) et supérieurs. DEBUG
est pratique à utiliser pendant le développement. TRACE
est bonne à utiliser lors de la débogage d'un problème spécifique, mais offre généralement trop d'informations à laisser.
Minlog prend en charge un niveau de journalisation fixe, ce qui fait que le compilateur Java supprime les instructions de journalisation en dessous de ce niveau au moment de la compilation. Kryo doit être compilé avec un pot de minlog de niveau de journalisation fixe.
Kryo n'est pas sûr de fil. Chaque thread doit avoir ses propres instances de kryo, d'entrée et de sortie.
Parce que Kryo n'est pas sûr de fil et la construction et la configuration d'une instance Kryo est relativement coûteuse, dans un environnement multithread, le threadlocal ou la mise en commun peut être pris en compte.
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 ();
Pour la mise en commun, Kryo fournit la classe de piscine qui peut mettre en commun Kryo, entrée, sortie ou instances de toute autre classe.
// 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 );
Si true
est passé comme le premier argument au constructeur de pool, le pool utilise la synchronisation en interne et est accessible par plusieurs threads simultanément.
Si true
est passé comme deuxième argument au constructeur de pool, le pool stocke des objets utilisant java.lang.ref.softreference. Cela permet aux objets de la piscine d'être collectés aux ordures lorsque la pression de la mémoire sur le JVM est élevée. Pool clean
supprime toutes les références douces dont l'objet a été collecté aux ordures. Cela peut réduire la taille de la piscine lorsqu'aucune capacité maximale n'a été définie. Lorsque la piscine a une capacité maximale, il n'est pas nécessaire d'appeler clean
car le free
arbitre essaiera de supprimer une référence vide si la capacité maximale a été atteinte.
Le troisième paramètre de pool est la capacité maximale. Si un objet est libéré et que le pool contient déjà le nombre maximum d'objets libres, l'objet spécifié est réinitialisé mais non ajouté au pool. La capacité maximale peut être omise pour aucune limite.
Si un objet implémente pool.poolable, reset
poolable est appelée lorsque l'objet est libéré. Cela donne à l'objet une chance de réinitialiser son état de réutilisation à l'avenir. Alternativement, reset
du pool peut être remplacée pour réinitialiser les objets. Implémentation d'entrée et de sortie Poolable pour définir leur position
et total
sur 0. Kryo ne met pas en œuvre poolable car son état de graphique d'objet est généralement réinitialisé automatiquement après chaque sérialisation (voir réinitialisation). Si vous désactivez la réinitialisation automatique via setAutoReset(false)
, assurez-vous d'appeler Kryo.reset()
avant de renvoyer l'instance à la pool.
Pool getFree
renvoie le nombre d'objets disponibles à obtenir. Si vous utilisez des références souples, ce nombre peut inclure des objets qui ont été collectés à la poubelle. clean
peut être utilisé en premier pour éliminer les références douces vides.
Pool getPeak
renvoie le plus grand nombre d'objets libres le plus élevé. Cela peut aider à déterminer si la capacité maximale d'une piscine est définie de manière appropriée. Il peut être réinitialisé à tout moment avec resetPeak
.
Kryo fournit un certain nombre de repères basés sur JMH et de fichiers R / GGPLOT2.
Kryo peut être comparé à de nombreuses autres bibliothèques de sérialisation dans le projet de sérialiseurs JVM. Les repères sont petits, datés et locaux plutôt que d'utiliser JMH, donc sont moins dignes de confiance. En outre, il est très difficile de comparer soigneusement les bibliothèques de sérialisation à l'aide d'une référence. Les bibliothèques ont de nombreuses fonctionnalités différentes et ont souvent des objectifs différents, ils peuvent donc exceller à résoudre des problèmes complètement différents. Pour comprendre ces repères, le code exécuté et les données en cours de sérialisation doivent être analysés et contrastés avec vos besoins spécifiques. Certains sérialiseurs sont hautement optimisés et utilisent des pages de code, d'autres n'utilisent que quelques lignes. C'est bon de montrer ce qui est possible, mais peut ne pas être une comparaison pertinente pour de nombreuses situations.
Il existe un certain nombre de projets utilisant Kryo. Quelques-uns sont répertoriés ci-dessous. Veuillez soumettre une demande de traction si vous souhaitez que votre projet soit inclus ici.