Kryo es un marco de serialización de gráficos de objetos binarios rápido y eficiente para Java. Los objetivos del proyecto son alta velocidad, tamaño reducido y una API fácil de usar. El proyecto es útil siempre que sea necesario conservar objetos, ya sea en un archivo, una base de datos o a través de la red.
Kryo también puede realizar copias/clonaciones profundas y superficiales automáticas. Se trata de una copia directa de un objeto a otro, no de un objeto a bytes a otro.
Esta documentación es para Kryo versión 5.x. Consulte la Wiki para la versión 4.x.
Utilice la lista de correo de Kryo para preguntas, debates y asistencia. Limite el uso del rastreador de problemas de Kryo a errores y mejoras, no a preguntas, discusiones o soporte.
Kryo publica dos tipos de artefactos/frascos:
Los Kryo JAR están disponibles en la página de lanzamientos y en Maven Central. Las últimas instantáneas de Kryo, incluidas las compilaciones de instantáneas del maestro, se encuentran en el repositorio de Sonatype.
Para usar la última versión de Kryo en su aplicación, use esta entrada de dependencia en su pom.xml
:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
Para usar la última versión de Kryo en una biblioteca que desea publicar, use esta entrada de dependencia en su pom.xml
:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
Para utilizar la última instantánea de Kryo, utilice:
< 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 >
No todo el mundo es fanático de Maven. Usar Kryo sin Maven requiere colocar el Kryo JAR en su classpath junto con los JAR de dependencia que se encuentran en lib.
Para construir Kryo desde el código fuente se requiere JDK11+ y Maven. Para construir todos los artefactos, ejecute:
mvn clean && mvn install
Avanzando para mostrar cómo se puede utilizar la biblioteca:
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 clase Kryo realiza la serialización automáticamente. Las clases de salida y entrada manejan bytes de almacenamiento en búfer y, opcionalmente, descargan a una secuencia.
El resto de este documento detalla cómo funciona esto y el uso avanzado de la biblioteca.
La entrada y salida de datos de Kryo se realiza mediante las clases de Entrada y Salida. Estas clases no son seguras para subprocesos.
La clase Output es un OutputStream que escribe datos en un búfer de matriz de bytes. Este búfer se puede obtener y utilizar directamente, si se desea una matriz de bytes. Si a la salida se le asigna un OutputStream, descargará los bytes a la secuencia cuando el búfer se llene; de lo contrario, la salida puede hacer crecer su búfer automáticamente. La salida tiene muchos métodos para escribir primitivas y cadenas de manera eficiente en bytes. Proporciona una funcionalidad similar a DataOutputStream, BufferedOutputStream, FilterOutputStream y ByteArrayOutputStream, todo en una sola clase.
Consejo: Salida y Entrada proporcionan todas las funciones de ByteArrayOutputStream. Rara vez hay una razón para que la salida se vacíe en un ByteArrayOutputStream.
La salida almacena en búfer los bytes cuando se escribe en un OutputStream, por lo que se debe llamar a flush
o close
después de que se completa la escritura para que los bytes almacenados en el búfer se escriban en OutputStream. Si a la salida no se le ha proporcionado un OutputStream, no es necesario llamar flush
o close
. A diferencia de muchas secuencias, una instancia de Salida se puede reutilizar estableciendo la posición o estableciendo una nueva matriz de bytes o secuencia.
Consejo: Dado que la salida ya tiene buffers, no hay razón para que la salida se vacíe en un BufferedOutputStream.
El constructor de salida de argumento cero crea una salida no inicializada. Se debe llamar setBuffer
de salida antes de poder utilizar la salida.
La clase Input es un InputStream que lee datos de un búfer de matriz de bytes. Este búfer se puede configurar directamente, si se desea leer desde una matriz de bytes. Si a la entrada se le asigna un InputStream, llenará el búfer de la secuencia cuando se hayan leído todos los datos del búfer. La entrada tiene muchos métodos para leer eficientemente primitivas y cadenas de bytes. Proporciona una funcionalidad similar a DataInputStream, BufferedInputStream, FilterInputStream y ByteArrayInputStream, todo en una sola clase.
Consejo: Input proporciona todas las funciones de ByteArrayInputStream. Rara vez hay una razón para leer la entrada de un ByteArrayInputStream.
Si se llama al close
de entrada, el flujo de entrada de la entrada se cierra, si lo hay. Si no lee desde un InputStream, entonces no es necesario llamar close
. A diferencia de muchas secuencias, una instancia de entrada se puede reutilizar estableciendo la posición y el límite, o configurando una nueva matriz de bytes o InputStream.
El constructor de entrada de argumento cero crea una entrada no inicializada. Se debe llamar setBuffer
de entrada antes de poder utilizar la entrada.
Las clases ByteBufferOutput y ByteBufferInput funcionan exactamente igual que Salida y Entrada, excepto que utilizan un ByteBuffer en lugar de una matriz de bytes.
Las clases UnsafeOutput, UnsafeInput, UnsafeByteBufferOutput y UnsafeByteBufferInput funcionan exactamente igual que sus contrapartes no inseguras, excepto que usan sun.misc.Unsafe para un mayor rendimiento en muchos casos. Para utilizar estas clases, Util.unsafe
debe ser verdadero.
La desventaja de utilizar buffers inseguros es que el endianismo nativo y la representación de tipos numéricos del sistema que realiza la serialización afectan los datos serializados. Por ejemplo, la deserialización fallará si los datos se escriben en X86 y se leen en SPARC. Además, si los datos se escriben con un búfer no seguro, se deben leer con un búfer no seguro.
La mayor diferencia de rendimiento con los buffers inseguros es con matrices primitivas grandes cuando no se utiliza codificación de longitud variable. La codificación de longitud variable se puede deshabilitar para los buffers no seguros o solo para campos específicos (cuando se usa FieldSerializer).
Las clases IO proporcionan métodos para leer y escribir valores int (varint) y long (varlong) de longitud variable. Esto se hace usando el octavo bit de cada byte para indicar si siguen más bytes, lo que significa que un varint usa de 1 a 5 bytes y un varlong usa de 1 a 9 bytes. Usar codificación de longitud variable es más costoso pero hace que los datos serializados sean mucho más pequeños.
Al escribir un valor de longitud variable, el valor se puede optimizar para valores positivos o para valores negativos y positivos. Por ejemplo, cuando se optimiza para valores positivos, del 0 al 127 se escribe en un byte, del 128 al 16383 en dos bytes, etc. Sin embargo, los números negativos pequeños son el peor de los casos con 5 bytes. Cuando no se optimizan para positivo, estos rangos se reducen a la mitad. Por ejemplo, -64 a 63 se escribe en un byte, 64 a 8191 y -65 a -8192 en dos bytes, etc.
Los buffers de entrada y salida proporcionan métodos para leer y escribir valores de tamaño fijo o de longitud variable. También existen métodos para permitir que el búfer decida si se escribe un valor de tamaño fijo o de longitud variable. Esto permite que el código de serialización garantice que se use codificación de longitud variable para valores muy comunes que inflarían la salida si se usara un tamaño fijo, al tiempo que permite que la configuración del búfer decida para todos los demás valores.
Método | Descripción |
---|---|
escribirInt(int) | Escribe un int de 4 bytes. |
escribirVarInt(int, booleano) | Escribe un int de 1 a 5 bytes. |
escribirInt(int, booleano) | Escribe un int de 4 o 1-5 bytes (el búfer decide). |
escribirLong(largo) | Escribe una longitud de 8 bytes. |
escribirVarLong(largo, booleano) | Escribe una longitud de 1 a 9 bytes. |
escribirLong(largo, booleano) | Escribe una longitud de 8 o 1-9 bytes (el búfer decide). |
Para deshabilitar la codificación de longitud variable para todos los valores, sería necesario anular los métodos writeVarInt
, writeVarLong
, readVarInt
y readVarLong
.
Puede resultar útil escribir la longitud de algunos datos y luego los datos. Cuando la longitud de los datos no se conoce de antemano, todos los datos deben almacenarse en un búfer para determinar su longitud, luego se puede escribir la longitud y luego los datos. El uso de un único búfer grande para esto impediría la transmisión y puede requerir un búfer excesivamente grande, lo cual no es ideal.
La codificación fragmentada resuelve este problema mediante el uso de un búfer pequeño. Cuando el búfer está lleno, se escribe su longitud y luego los datos. Este es un trozo de datos. El búfer se borra y esto continúa hasta que no haya más datos para escribir. Un fragmento con una longitud de cero indica el final de los fragmentos.
Kryo ofrece clases para facilitar el uso de la codificación fragmentada. OutputChunked se utiliza para escribir datos fragmentados. Extiende Salida, por lo que tiene todos los métodos convenientes para escribir datos. Cuando el búfer OutputChunked está lleno, vacía el fragmento a otro OutputStream. El método endChunk
se utiliza para marcar el final de un conjunto de fragmentos.
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 ();
Para leer los datos fragmentados, se utiliza InputChunked. Amplía la entrada, por lo que tiene todos los métodos convenientes para leer datos. Al leer, parecerá que InputChunked llega al final de los datos cuando llega al final de un conjunto de fragmentos. El método nextChunks
avanza al siguiente conjunto de fragmentos, incluso si no se han leído todos los datos del conjunto de fragmentos actual.
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 ();
Generalmente, la salida y la entrada proporcionan un buen rendimiento. Los buffers inseguros funcionan igual o mejor, especialmente para matrices primitivas, si sus incompatibilidades entre plataformas son aceptables. ByteBufferOutput y ByteBufferInput proporcionan un rendimiento ligeramente peor, pero esto puede ser aceptable si el destino final de los bytes debe ser un ByteBuffer.
La codificación de longitud variable es más lenta que la de valores fijos, especialmente cuando hay muchos datos que la utilizan.
La codificación fragmentada utiliza un búfer intermediario, por lo que agrega una copia adicional de todos los bytes. Esto por sí solo puede ser aceptable; sin embargo, cuando se usa en un serializador reentrante, el serializador debe crear un OutputChunked o un InputChunked para cada objeto. La asignación y la recolección de basura de esos buffers durante la serialización pueden tener un impacto negativo en el rendimiento.
Kryo tiene tres conjuntos de métodos para leer y escribir objetos. Si no se conoce la clase concreta del objeto y el objeto podría ser nulo:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
Si se conoce la clase y el objeto podría ser nulo:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
Si se conoce la clase y el objeto no puede ser nulo:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
Todos estos métodos primero encuentran el serializador apropiado para usar y luego lo usan para serializar o deserializar el objeto. Los serializadores pueden llamar a estos métodos para la serialización recursiva. Kryo maneja automáticamente múltiples referencias al mismo objeto y referencias circulares.
Además de los métodos para leer y escribir objetos, la clase Kryo proporciona una forma de registrar serializadores, lee y escribe identificadores de clase de manera eficiente, maneja objetos nulos para serializadores que no pueden aceptar nulos y maneja la lectura y escritura de referencias de objetos (si están habilitadas). Esto permite a los serializadores centrarse en sus tareas de serialización.
Mientras prueba y explora las API de Kryo, puede resultar útil escribir un objeto en bytes y luego leer esos bytes nuevamente en un objeto.
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 );
En este ejemplo, la salida comienza con un búfer que tiene una capacidad de 1024 bytes. Si se escriben más bytes en la salida, el tamaño del búfer aumentará sin límite. No es necesario cerrar la salida porque no se le ha asignado un OutputStream. La entrada lee directamente desde el búfer de byte[]
de la salida.
Kryo admite la realización de copias profundas y superficiales de objetos mediante la asignación directa de un objeto a otro. Esto es más eficiente que serializar en bytes y volver a objetos.
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
Todos los serializadores que se utilizan deben admitir la copia. Todos los serializadores provistos con Kryo admiten copia.
Al igual que con la serialización, al copiar, Kryo maneja automáticamente múltiples referencias al mismo objeto y referencias circulares si las referencias están habilitadas.
Si utiliza Kryo sólo para copiar, el registro se puede desactivar de forma segura.
Kryo getOriginalToCopyMap
se puede utilizar después de copiar un gráfico de objetos para obtener un mapa de objetos antiguos a nuevos. El mapa se borra automáticamente mediante Kryo reset
, por lo que solo es útil cuando Kryo setAutoReset
es falso.
Por defecto las referencias no están habilitadas. Esto significa que si un objeto aparece en un gráfico de objetos varias veces, se escribirá varias veces y se deserializará como objetos múltiples y diferentes. Cuando las referencias están deshabilitadas, las referencias circulares harán que falle la serialización. Las referencias se habilitan o deshabilitan con Kryo setReferences
para serialización y setCopyReferences
para copiar.
Cuando las referencias están habilitadas, se escribe una variante antes de cada objeto la primera vez que aparece en el gráfico de objetos. Para apariciones posteriores de esa clase dentro del mismo gráfico de objetos, solo se escribe una variante. Después de la deserialización, las referencias de objetos se restauran, incluidas las referencias circulares. Los serializadores en uso deben admitir referencias llamando reference
de Kryo en Serializer read
.
Habilitar referencias afecta el rendimiento porque es necesario realizar un seguimiento de cada objeto que se lee o escribe.
En secreto, un ReferenceResolver maneja el seguimiento de objetos que han sido leídos o escritos y proporciona ID de referencia int. Se proporcionan múltiples implementaciones:
useReferences(Class)
se puede anular. Devuelve un valor booleano para decidir si se admiten referencias para una clase. Si una clase no admite referencias, el ID de referencia de la variante no se escribe antes de los objetos de ese tipo. Si una clase no necesita referencias y los objetos de ese tipo aparecen en el gráfico de objetos muchas veces, el tamaño serializado se puede reducir en gran medida deshabilitando las referencias para esa clase. El solucionador de referencias predeterminado devuelve falso para todos los contenedores y enumeraciones primitivos. También es común devolver false para String y otras clases, dependiendo de los gráficos de objetos que se serializan.
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
El solucionador de referencias determina el número máximo de referencias en un gráfico de un solo objeto. Los índices de matrices de Java están limitados a Integer.MAX_VALUE
, por lo que los solucionadores de referencia que utilizan estructuras de datos basadas en matrices pueden generar una java.lang.NegativeArraySizeException
al serializar más de ~2 mil millones de objetos. Kryo usa ID de clase int, por lo que el número máximo de referencias en un gráfico de un solo objeto se limita al rango completo de números positivos y negativos en un int (~4 mil millones).
Kryo getContext
devuelve un mapa para almacenar datos del usuario. La instancia de Kryo está disponible para todos los serializadores, por lo que todos los serializadores pueden acceder fácilmente a estos datos.
Kryo getGraphContext
es similar, pero se borra después de serializar o deserializar cada gráfico de objeto. Esto facilita la gestión del estado que solo es relevante para el gráfico de objetos actual. Por ejemplo, esto se puede utilizar para escribir algunos datos de esquema la primera vez que se encuentra una clase en un gráfico de objetos. Consulte CompatibleFieldSerializer para ver un ejemplo.
De forma predeterminada, se llama reset
de Kryo después de serializar cada gráfico de objeto completo. Esto restablece los nombres de clases no registrados en el solucionador de clases, las referencias a objetos previamente serializados o deserializados en el solucionador de referencias y borra el contexto del gráfico. Kryo setAutoReset(false)
se puede usar para deshabilitar la llamada reset
automáticamente, permitiendo que ese estado abarque múltiples gráficos de objetos.
Kryo es un marco para facilitar la serialización. El marco en sí no impone un esquema ni le importa qué o cómo se escriben o leen los datos. Los serializadores son conectables y toman decisiones sobre qué leer y escribir. Muchos serializadores se proporcionan listos para usar para leer y escribir datos de varias maneras. Si bien los serializadores proporcionados pueden leer y escribir la mayoría de los objetos, se pueden reemplazar fácilmente parcial o completamente con sus propios serializadores.
Cuando Kryo va a escribir una instancia de un objeto, primero puede que necesite escribir algo que identifique la clase del objeto. De forma predeterminada, todas las clases que Kryo leerá o escribirá deben registrarse de antemano. El registro proporciona un ID de clase int, el serializador que se usará para la clase y el instanciador de objeto usado para crear instancias de la clase.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Durante la deserialización, las clases registradas deben tener exactamente los mismos ID que tenían durante la serialización. Cuando se registra, a una clase se le asigna el siguiente ID entero más bajo disponible, lo que significa que el orden en que se registran las clases es importante. Opcionalmente, el ID de clase se puede especificar explícitamente para que el orden no sea importante:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
Los ID de clase -1 y -2 están reservados. Los ID de clase 0-8 se utilizan de forma predeterminada para tipos primitivos y cadenas, aunque estos ID se pueden reutilizar. Los ID se escriben como variantes optimizadas positivas, por lo que son más eficientes cuando son números enteros pequeños y positivos. Las identificaciones negativas no se serializan de manera eficiente.
Debajo de las sábanas, un ClassResolver se encarga de leer y escribir bytes para representar una clase. La implementación predeterminada es suficiente en la mayoría de los casos, pero se puede reemplazar para personalizar lo que sucede cuando se registra una clase, lo que sucede cuando se encuentra una clase no registrada durante la serialización y lo que se lee y escribe para representar una clase.
Kryo se puede configurar para permitir la serialización sin registrar clases por adelantado.
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
El uso de clases registradas y no registradas puede combinarse. Las clases no registradas tienen dos inconvenientes importantes:
Si utiliza Kryo sólo para copiar, el registro se puede desactivar de forma segura.
Cuando no es necesario registrarse, se puede habilitar Kryo setWarnUnregisteredClasses
para registrar un mensaje cuando se encuentre una clase no registrada. Esto se puede utilizar para obtener fácilmente una lista de todas las clases no registradas. Kryo unregisteredClassMessage
se puede anular para personalizar el mensaje de registro o realizar otras acciones.
Cuando se registra una clase, opcionalmente se puede especificar una instancia de serializador. Durante la deserialización, las clases registradas deben tener exactamente los mismos serializadores y configuraciones de serializador que tenían durante la serialización.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
Si no se especifica un serializador o cuando se encuentra una clase no registrada, se elige un serializador automáticamente de una lista de "serializadores predeterminados" que asigna una clase a un serializador. Tener muchos serializadores predeterminados no afecta el rendimiento de la serialización, por lo que, de forma predeterminada, Kryo tiene más de 50 serializadores predeterminados para varias clases de JRE. Se pueden agregar serializadores predeterminados adicionales:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Esto hará que se cree una instancia de SomeSerializer cuando se registre SomeClass o cualquier clase que extienda o implemente SomeClass.
Los serializadores predeterminados se ordenan de modo que las clases más específicas coincidan primero, pero, por lo demás, coinciden en el orden en que se agregan. El orden en que se agregan puede ser relevante para las interfaces.
Si ningún serializador predeterminado coincide con una clase, entonces se utiliza el serializador predeterminado global. El serializador predeterminado global está configurado en FieldSerializer de forma predeterminada, pero se puede cambiar. Por lo general, el serializador global puede manejar muchos tipos diferentes.
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
Con este código, suponiendo que ningún serializador predeterminado coincida con SomeClass, se utilizará TaggedFieldSerializer.
Una clase también puede usar la anotación DefaultSerializer, que se usará en lugar de elegir uno de los serializadores predeterminados de Kryo:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
Para obtener la máxima flexibilidad, Kryo getDefaultSerializer
se puede anular para implementar una lógica personalizada para elegir y crear instancias de un serializador.
El método addDefaultSerializer(Class, Class)
no permite la configuración del serializador. Se puede configurar una fábrica de serializadores en lugar de una clase de serializador, lo que permite a la fábrica crear y configurar cada instancia de serializador. Se proporcionan fábricas para serializadores comunes, a menudo con un método getConfig
para configurar los serializadores que se crean.
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 fábrica de serializadores tiene un método isSupported(Class)
que le permite negarse a manejar una clase, incluso si coincide con la clase. Esto permite que una fábrica verifique múltiples interfaces o implemente otra lógica.
Si bien algunos serializadores son para una clase específica, otros pueden serializar muchas clases diferentes. Los serializadores pueden usar Kryo newInstance(Class)
para crear una instancia de cualquier clase. Esto se hace buscando el registro de la clase y luego usando el ObjectInstantiator del registro. El instanciador se puede especificar en el registro.
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Si el registro no tiene un instanciador, Kryo newInstantiator
proporciona uno. Para personalizar cómo se crean los objetos, se puede anular Kryo newInstantiator
o proporcionar una InstantiatorStrategy.
Kryo proporciona DefaultInstantiatorStrategy que crea objetos usando ReflectASM para llamar a un constructor de argumento cero. Si eso no es posible, utiliza la reflexión para llamar a un constructor de argumento cero. Si eso también falla, entonces arroja una excepción o intenta una InstantiatorStrategy alternativa. Reflection usa setAccessible
, por lo que un constructor privado de argumento cero puede ser una buena manera de permitir que Kryo cree instancias de una clase sin afectar la API pública.
DefaultInstantiatorStrategy es la forma recomendada de crear objetos con Kryo. Ejecuta constructores tal como se haría con el código Java. También se pueden utilizar mecanismos alternativos extralingüísticos para crear objetos. Objenesis StdInstantiatorStrategy utiliza API específicas de JVM para crear una instancia de una clase sin llamar a ningún constructor. Usar esto es peligroso porque la mayoría de las clases esperan que se llame a sus constructores. Crear el objeto omitiendo sus constructores puede dejar el objeto en un estado no inicializado o no válido. Las clases deben diseñarse para crearse de esta manera.
Kryo se puede configurar para probar DefaultInstantiatorStrategy primero y luego recurrir a StdInstantiatorStrategy si es necesario.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
Otra opción es SerializingInstantiatorStrategy, que utiliza el mecanismo de serialización integrado de Java para crear una instancia. Al utilizar esto, la clase debe implementar java.io.Serializable y se invoca el primer constructor de argumento cero en una superclase. Esto también pasa por alto a los constructores y, por lo tanto, es peligroso por las mismas razones que StdInstantiatorStrategy.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
Alternativamente, algunos serializadores genéricos proporcionan métodos que se pueden anular para personalizar la creación de objetos para un tipo específico, en lugar de llamar a 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 );
}
});
Algunos serializadores proporcionan un método writeHeader
que se puede anular para escribir los datos necesarios para create
en el momento adecuado.
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 serializador no proporciona writeHeader
, la escritura de datos para create
se puede realizar 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 ());
}
}
Incluso cuando un serializador conoce la clase esperada para un valor (por ejemplo, la clase de un campo), si la clase concreta del valor no es definitiva, entonces el serializador debe escribir primero el ID de la clase y luego el valor. Las clases finales se pueden serializar de manera más eficiente porque no son polimórficas.
Kryo isFinal
se utiliza para determinar si una clase es definitiva. Este método se puede anular para que devuelva verdadero incluso para tipos que no son definitivos. Por ejemplo, si una aplicación usa ArrayList ampliamente pero nunca usa una subclase ArrayList, tratar ArrayList como final podría permitir que FieldSerializer guarde entre 1 y 2 bytes por campo ArrayList.
Kryo puede serializar cierres de Java 8+ que implementan java.io.Serializable, con algunas advertencias. Es posible que los cierres serializados en una JVM no se deserialicen en una JVM diferente.
Kryo isClosure
se utiliza para determinar si una clase es un cierre. Si es así, se utiliza ClosureSerializer.Closure para encontrar el registro de clase en lugar de la clase de cierre. Para serializar cierres, se deben registrar las siguientes clases: CierreSerializer.Closure, Objeto[] y Clase. Además, se debe registrar la clase de captura del cierre.
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 );
Es posible serializar cierres que no implementan Serializable con algo de esfuerzo.
Kryo admite transmisiones, por lo que es trivial utilizar compresión o cifrado en todos los bytes serializados:
OutputStream outputStream = new DeflaterOutputStream ( new FileOutputStream ( "file.bin" ));
Output output = new Output ( outputStream );
Kryo kryo = new Kryo ();
kryo . writeObject ( output , object );
output . close ();
Si es necesario, se puede utilizar un serializador para comprimir o cifrar los bytes de solo un subconjunto de bytes de un gráfico de objetos. Por ejemplo, consulte DeflateSerializer o BlowfishSerializer. Estos serializadores envuelven otro serializador para codificar y decodificar los bytes.
La clase abstracta Serializer define métodos para pasar de objetos a bytes y de bytes a objetos.
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 ());
}
}
El serializador tiene solo dos métodos que deben implementarse. write
escribe el objeto como bytes en la salida. read
crea una nueva instancia del objeto y lee desde la Entrada para completarlo.
Cuando se usa Kryo para leer un objeto anidado en read
del serializador, primero se debe llamar a reference
de Kryo con el objeto principal si es posible que el objeto anidado haga referencia al objeto principal. No es necesario llamar a reference
de Kryo si los objetos anidados no pueden hacer referencia al objeto principal, si Kryo no se utiliza para objetos anidados o si no se utilizan referencias. Si los objetos anidados pueden usar el mismo serializador, el serializador debe ser reentrante.
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 ;
}
Los serializadores normalmente no deberían hacer uso directo de otros serializadores, sino que deberían utilizar los métodos de lectura y escritura de Kryo. Esto permite a Kryo organizar la serialización y manejar funciones como referencias y objetos nulos. A veces, un serializador sabe qué serializador usar para un objeto anidado. En ese caso, debería utilizar los métodos de lectura y escritura de Kryo que aceptan un serializador.
Si el objeto pudiera ser nulo:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
Si el objeto no puede ser nulo:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
Durante la serialización, Kryo getDepth
proporciona la profundidad actual del gráfico de objetos.
Cuando falla una serialización, se puede generar una KryoException con información de seguimiento de serialización sobre en qué parte del gráfico de objetos ocurrió la excepción. Cuando se utilizan serializadores anidados, se puede detectar KryoException para agregar información de seguimiento de serialización.
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 ;
}
}
Los serializadores que proporciona Kryo utilizan la pila de llamadas al serializar objetos anidados. Kryo minimiza las llamadas a la pila, pero puede ocurrir un desbordamiento de la pila para gráficos de objetos extremadamente profundos. Este es un problema común para la mayoría de las bibliotecas de serialización, incluida la serialización Java incorporada. El tamaño de la pila se puede aumentar usando -Xss
, pero tenga en cuenta que esto se aplica a todos los subprocesos. Los tamaños de pila grandes en una JVM con muchos subprocesos pueden utilizar una gran cantidad de memoria.
Kryo setMaxDepth
se puede utilizar para limitar la profundidad máxima de un gráfico de objetos. Esto puede evitar que datos maliciosos provoquen un desbordamiento de pila.
De forma predeterminada, los serializadores nunca recibirán un valor nulo; en cambio, Kryo escribirá un byte según sea necesario para indicar nulo o no nulo. Si un serializador puede ser más eficiente manejando valores nulos por sí mismo, puede llamar al serializador setAcceptsNull(true)
. Esto también se puede usar para evitar escribir el byte que indica nulo cuando se sabe que todas las instancias que manejará el serializador nunca serán nulas.
Kryo getGenerics
proporciona información de tipo genérico para que los serializadores puedan ser más eficientes. Esto se usa más comúnmente para evitar escribir la clase cuando la clase de parámetro de tipo es final.
La inferencia de tipos genéricos está habilitada de forma predeterminada y se puede deshabilitar con Kryo setOptimizedGenerics(false)
. Deshabilitar la optimización de genéricos puede aumentar el rendimiento a costa de un tamaño serializado mayor.
Si la clase tiene un único parámetro de tipo, nextGenericClass
devuelve la clase de parámetro de tipo, o nulo si no hay ninguno. Después de leer o escribir cualquier objeto anidado, se debe llamar popGenericType
. Consulte CollectionSerializer para ver un ejemplo.
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 ;
}
}
Para una clase con múltiples parámetros de tipo, nextGenericTypes
devuelve una matriz de instancias de GenericType y se usa resolve
para obtener la clase para cada GenericType. Después de leer o escribir cualquier objeto anidado, se debe llamar popGenericType
. Consulte MapSerializer para ver un ejemplo.
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 ;
}
}
Para los serializadores que pasan información de parámetros de tipo para objetos anidados en el gráfico de objetos (uso algo avanzado), primero se usa GenericsHierarchy para almacenar los parámetros de tipo para una clase. Durante la serialización, se llama a Generics pushTypeVariables
antes de que se resuelvan los tipos genéricos (si los hay). Si se devuelve >0, esto debe ir seguido de Generics popTypeVariables
. Consulte FieldSerializer para ver un ejemplo.
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 ;
}
}
En lugar de utilizar un serializador, una clase puede optar por realizar su propia serialización implementando KryoSerializable (similar a 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 );
}
}
Obviamente, la instancia ya debe estar creada antes de poder llamar read
, por lo que la clase no puede controlar su propia creación. Una clase KryoSerializable utilizará el serializador predeterminado KryoSerializableSerializer, que utiliza Kryo newInstance
para crear una nueva instancia. Es trivial escribir su propio serializador para personalizar el proceso, llamar a métodos antes o después de la serialización, etc.
Los serializadores solo admiten la copia si se anula copy
. Similar a Serializer read
, este método contiene la lógica para crear y configurar la copia. Al igual que read
, se debe llamar reference
de Kryo antes de usar Kryo para copiar objetos secundarios, si alguno de los objetos secundarios pudiera hacer referencia al objeto principal.
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 ;
}
}
En lugar de utilizar un serializador, las clases pueden implementar KryoCopyable para realizar sus propias copias:
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 ;
}
}
El serializador setImmutable(true)
se puede utilizar cuando el tipo es inmutable. En ese caso, no es necesario implementar copy
del serializador; la implementación copy
predeterminada devolverá el objeto original.
Se aplican las siguientes reglas generales a la numeración de versiones de Kryo:
La actualización de cualquier dependencia es un evento significativo, pero una biblioteca de serialización es más propensa a la rotura que la mayoría de las dependencias. Al actualizar KRYO, verifique las diferencias de la versión y pruebe la nueva versión a fondo en sus propias aplicaciones. Tratamos de hacerlo lo más seguro y fácil posible.
Los serializadores KRYO proporcionados por defecto suponen que Java se utilizará para la deserialización, por lo que no definen explícitamente el formato escrito. Los serializadores podrían escribirse utilizando un formato estandarizado que otros idiomas leen más fácilmente, pero esto no se proporciona de forma predeterminada.
Para algunas necesidades, como el almacenamiento a largo plazo de bytes serializados, puede ser importante cómo la serialización maneja los cambios en las clases. Esto se conoce como compatibilidad de avance (lectura de bytes serializados por clases más nuevas) y compatibilidad con atraso (lectura de bytes serializados por clases más antiguas). Kryo proporciona algunos serializadores genéricos que adoptan diferentes enfoques para manejar la compatibilidad. Los serializadores adicionales se pueden desarrollar fácilmente para la compatibilidad hacia adelante y hacia atrás, como un serializador que utiliza un esquema externo escrito a mano.
Cuando una clase cambia más de lo que su serializador puede manejar, se puede escribir un serializador para transferir los datos a una clase diferente. Todo el uso de la clase anterior en el código de aplicación debe ser reemplazado por la nueva clase. La vieja clase se mantiene únicamente para este serializador.
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 proporciona a muchos serializadores con diversas opciones de configuración y niveles de compatibilidad. Se pueden encontrar serializadores adicionales en el Proyecto Hermano Kryo-Serializers, que alberga serializadores que acceden a las API privadas o no son perfectamente seguras en todas las JVM. Se pueden encontrar más serializadores en la sección de enlaces.
Fieldserializer funciona serializando cada campo no transitorio. Puede serializar Pojos y muchas otras clases sin ninguna configuración. Todos los campos no públicos se escriben y leen de forma predeterminada, por lo que es importante evaluar cada clase que será serializada. Si los campos son públicos, la serialización puede ser más rápida.
Fieldserializer es eficiente escribiendo solo los datos de campo, sin ninguna información de esquema, utilizando los archivos de clase Java como esquema. No es compatible con agregar, eliminar o cambiar el tipo de campos sin invalidar los bytes serializados previamente. El cambio de nombre de los campos se permite solo si no cambia el orden alfabético de los campos.
Los inconvenientes de compatibilidad de Fieldserializer pueden ser aceptables en muchas situaciones, como cuando se envían datos a través de una red, pero pueden no ser una buena opción para el almacenamiento de datos a largo plazo porque las clases de Java no pueden evolucionar. En muchos casos, TaggedFieldserializer es una mejor opción.
Configuración | Descripción | Valor predeterminado |
---|---|---|
fieldsCanBeNull | Cuando se supone que no se supone que no hay valores de campo nulos, lo que puede ahorrar 0-1 byte por campo. | verdadero |
setFieldsAsAccessible | Cuando sea verdadero, todos los campos no transitorios (incluidos los campos privados) serán serializados y setAccessible si es necesario. Si False, solo los campos en la API público serán serializados. | verdadero |
ignoreSyntheticFields | Si es cierto, los campos sintéticos (generados por el compilador para el alcance) se serializan. | FALSO |
fixedFieldTypes | Si es cierto, se supone que el tipo de concreto de cada valor de campo coincide con el tipo de campo. Esto elimina la necesidad de escribir la ID de clase para los valores de campo. | FALSO |
copyTransient | Si es cierto, se copiarán todos los campos transitorios. | verdadero |
serializeTransient | Si es cierto, los campos transitorios se serializarán. | FALSO |
variableLengthEncoding | Si es verdadero, los valores de longitud variable se utilizan para los campos int y largos. | verdadero |
extendedFieldNames | Si es cierto, los nombres de campo están prefijados por su clase de declaración. Esto puede evitar conflictos cuando una subclase tiene un campo con el mismo nombre que una súper clase. | FALSO |
Fieldserializer proporciona los campos que serán serializados. Los campos se pueden eliminar, por lo que no serán serializados. Los campos se pueden configurar para que la serialiación sea más eficiente.
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 ());
Configuración | Descripción | Valor predeterminado |
---|---|---|
canBeNull | Cuando se supone que el valor del campo nunca es nulo, lo que puede ahorrar 0-1 byte. | verdadero |
valueClass | Establece la clase de concreto y el serializador para usar para el valor de campo. Esto elimina la necesidad de escribir la ID de clase para el valor. Si la clase del valor del campo es un envoltorio primitivo y primitivo o final, esta configuración predeterminada es la clase del campo. | nulo |
serializer | Establece el serializador para usar para el valor de campo. Si se establece el serializador, algunos serializadores requirieron que la clase de valor también se establezca. Si es nulo, se utilizará el serializador registrado con KRYO para la clase del valor de campo. | nulo |
variableLengthEncoding | Si es verdadero, se utilizan valores de longitud variable. Esto solo se aplica a los campos int o largos. | verdadero |
optimizePositive | Si es verdadero, los valores positivos se optimizan para valores de longitud variable. Esto solo se aplica a los campos int o largos cuando se usa la codificación de longitud variable. | verdadero |
Las anotaciones se pueden usar para configurar los serializadores para cada campo.
Anotación | Descripción |
---|---|
@Bind | Establece la configuración de Cachedfield para cualquier campo. |
@CollectionBind | Establece la configuración de coleccionistas para campos de recolección. |
@MapBind | Establece la configuración del mapserializador para los campos de mapas. |
@NotNull | Marca un campo como nunca ser nulo. |
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 extiende FieldSerializer y proporciona compatibilidad con versiones anteriores. Esto significa que los campos se pueden agregar sin invalidar bytes serializados previamente. No se admite eliminar, cambiar el nombre o cambiar el tipo de campo.
Cuando se agrega un campo, debe tener la anotación @Since(int)
para indicar la versión que se agregó para ser compatible con bytes serializados previamente. El valor de anotación nunca debe cambiar.
VersionFieldserializer agrega muy poca sobrecarga a Fieldserializer: un solo Varint adicional.
Configuración | Descripción | Valor predeterminado |
---|---|---|
compatible | Cuando falsa, se lanza una excepción al leer un objeto con una versión diferente. La versión de un objeto es la versión máxima de cualquier campo. | verdadero |
VersionFieldSerializer también hereda todas las configuraciones de FieldSerializer.
TaggedFieldserializer extiende Fieldserializer para proporcionar compatibilidad con versiones anteriores y compatibilidad opcional hacia adelante. Esto significa que los campos se pueden agregar o renombrar y eliminar opcionalmente sin invalidar bytes previamente serializados. Cambiar el tipo de campo no es compatible.
Solo los campos que tienen una anotación @Tag(int)
se serializan. Los valores de la etiqueta de campo deben ser únicos, tanto dentro de una clase como en todas sus súper clases. Se lanza una excepción si se encuentran valores de etiqueta duplicados.
La compatibilidad hacia adelante y hacia atrás y el rendimiento de la serialización depende de la configuración readUnknownTagData
y chunkedEncoding
. Además, se escribe un Varint antes de cada campo para el valor de la etiqueta.
Cuando readUnknownTagData
y chunkedEncoding
son falsos, los campos no deben eliminarse, pero se puede aplicar la anotación @Deprecated
. Los campos desapercibidos se leen cuando leen bytes antiguos pero no se escriben en nuevos bytes. Las clases pueden evolucionar leyendo los valores de los campos desapercibidos y escribiéndolos en otro lugar. Los campos pueden renombrarse y/o hacer privados para reducir el desorden en la clase (por ejemplo, ignored1
, ignored2
).
TaggedFieldserializer (con readUnknownTagData
y Falso chunkedEncoding
) es el serializador sugerido para la mayoría de las clases donde los campos pueden ser anotados. Permite que las clases evolucionen y los campos se eliminen de los datos serializados (a través de la deprecación), satisfacen las necesidades de la mayoría de las aplicaciones sin agregar mucho al tamaño serializado.
Configuración | Descripción | Valor predeterminado |
---|---|---|
readUnknownTagData | Cuando se encuentra una etiqueta falsa y desconocida, se lanza una excepción o, si chunkedEncoding es verdadero, los datos se omiten.Cuando es cierto, la clase para cada valor de campo se escribe antes del valor. Cuando se encuentra una etiqueta desconocida, se realiza un intento de leer los datos. Esto se utiliza para omitir los datos y, si las referencias están habilitadas, cualquier otro valor en el gráfico de objetos que hace referencia a que los datos aún pueden estar deserializados. Si la lectura falla los datos (por ejemplo, la clase se desconoce o se ha eliminado), entonces se lanza una excepción o, si chunkedEncoding es verdadero, los datos se omiten.En cualquier caso, si los datos se omiten y las referencias están habilitadas, entonces cualquier referencia en los datos omitidos no se lee y la deserialización adicional puede recibir las referencias incorrectas y fallar. | FALSO |
chunkedEncoding | Cuando es verdadero, los campos se escriben con codificación gruesa para permitir que los datos de campo desconocidos se omitan. Esto afecta el rendimiento. | FALSO |
chunkSize | El tamaño máximo de cada fragmento para la codificación fragmentada. | 1024 |
TaggedFieldserializer también hereda todas las configuraciones de Fieldserializer.
CompatibleFieldSerializer extiende el campos de los campos para proporcionar compatibilidad tanto hacia adelante como hacia atrás. Esto significa que los campos se pueden agregar o eliminar sin invalidar bytes previamente serializados. No se admite renombrar o cambiar el tipo de campo. Al igual que Fieldserializer, puede serializar la mayoría de las clases sin necesidad de anotaciones.
La compatibilidad hacia adelante y hacia atrás y el rendimiento de la serialización dependen de la configuración readUnknownFieldData
y chunkedEncoding
. Además, la primera vez que se encuentra la clase en los bytes serializados, se escribe un esquema simple que contiene las cadenas de nombres de campo. Debido a que los datos de campo se identifican por nombre, si una súper clase tiene un campo con el mismo nombre que una subclase, extendedFieldNames
deben ser verdaderos.
Configuración | Descripción | Valor predeterminado |
---|---|---|
readUnknownFieldData | Cuando se encuentra un campo falso y desconocido, se lanza una excepción o, si chunkedEncoding es verdadero, los datos se omiten.Cuando es cierto, la clase para cada valor de campo se escribe antes del valor. Cuando se encuentra un campo desconocido, se realiza un intento de leer los datos. Esto se utiliza para omitir los datos y, si las referencias están habilitadas, cualquier otro valor en el gráfico de objetos que hace referencia a que los datos aún pueden estar deserializados. Si la lectura de los datos falla (por ejemplo, se desconoce la clase o se ha eliminado), entonces se lanza una excepción o, si chunkedEncoding es verdadero, los datos se omiten.En cualquier caso, si los datos se omiten y las referencias están habilitadas, entonces cualquier referencia en los datos omitidos no se lee y la deserialización adicional puede recibir las referencias incorrectas y fallar. | verdadero |
chunkedEncoding | Cuando es verdadero, los campos se escriben con codificación gruesa para permitir que los datos de campo desconocidos se omitan. Esto afecta el rendimiento. | FALSO |
chunkSize | El tamaño máximo de cada fragmento para la codificación fragmentada. | 1024 |
Compatiblefieldserializer también hereda todas las configuraciones de campos de campos.
Beanserializer es muy similar al FieldSerializer, excepto que utiliza métodos de Getter y Setter en lugar del acceso directo al campo. Esto es un poco más lento, pero puede ser más seguro porque utiliza la API pública para configurar el objeto. Al igual que FieldSerializer, no proporciona compatibilidad hacia adelante o hacia atrás.
CollectionSerializer esenciarios que implementan la interfaz java.util.collection.
Configuración | Descripción | Valor predeterminado |
---|---|---|
elementsCanBeNull | Cuando se supone que no se supone que no hay elementos en la colección, lo que puede ahorrar 0-1 byte por elemento. | verdadero |
elementClass | Establece la clase de concreto para usar para cada elemento en la colección. Esto elimina la necesidad de escribir la ID de clase para cada elemento. Si la clase de elementos se conoce (por ejemplo, a través de genéricos) y un envoltorio primitivo y primitivo, entonces el colectionserializer no escribirá la ID de clase incluso cuando esta configuración es nula. | nulo |
elementSerializer | Establece el serializador para usar para cada elemento de la colección. Si se establece el serializador, algunos serializadores requirieron que la clase de valor también se establezca. Si es nulo, se utilizará el serializador registrado con KRyo para la clase de cada elemento. | nulo |
Mapserializer serializa objetos que implementan la interfaz java.util.map.
Configuración | Descripción | Valor predeterminado |
---|---|---|
keysCanBeNull | Cuando se supone que no se supone que no hay claves en el mapa, lo que puede ahorrar 0-1 byte por entrada. | verdadero |
valuesCanBeNull | Cuando se supone que no se supone que no hay valores en el mapa, lo que puede ahorrar 0-1 byte por entrada. | verdadero |
keyClass | Establece la clase concreta para usar para cada clave del mapa. Esto elimina la necesidad de escribir la ID de clase para cada clave. | nulo |
valueClass | Establece la clase concreta para usar para cada valor en el mapa. Esto elimina la necesidad de escribir la ID de clase para cada valor. | nulo |
keySerializer | Establece el serializador para usar para cada clave del mapa. Si se establece el serializador de valor, algunos serializadores requirieron que se establezca la clase de valor. Si es nulo, se utilizará el serializador registrado con KRyo para la clase de cada clave. | nulo |
valueSerializer | Establece el serializador para usar para cada valor en el mapa. Si se establece el serializador clave, algunos serializadores requirieron que se establezca la clase de valor. Si es nulo, se utilizará el serializador registrado con KRyo para la clase de cada valor. | nulo |
Javaserializer y ExternalizableSerializer son serializadores de KRyo que utiliza la serialización incorporada de Java. Esto es tan lento como de costumbre de Java, pero puede ser necesario para las clases heredadas.
java.io.externalizable y java.io.serializable no tienen serializadores predeterminados establecidos de forma predeterminada, por lo que los serializadores predeterminados deben establecerse manualmente o los serializadores establecidos cuando la clase está registrada.
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 hace uso de la biblioteca de registro de Minlog de bajo peso de bajo o superior. El nivel de registro se puede establecer mediante uno de los siguientes métodos:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
KRYO no hace registro en INFO
(el valor predeterminado) y los niveles anteriores. DEBUG
es conveniente de usar durante el desarrollo. Es bueno usar TRACE
al depurar un problema específico, pero generalmente genera demasiada información para dejar.
Minlog admite un nivel de registro fijo, que hace que el compilador Java elimine las declaraciones de registro por debajo de ese nivel en el momento de la compilación. KRYO debe compilarse con un frasco Minlog de nivel de registro fijo.
Kryo no es seguro. Cada hilo debe tener sus propias instancias de kryo, entrada y salida.
Debido a que KRYO no es seguro de subprocesos y la construcción y la configuración de una instancia de KRYO es relativamente costoso, en un entorno multiproceso se puede considerar el hilo o la agrupación.
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 ();
Para la agrupación, Kryo proporciona la clase de piscina que puede agrupar KRYO, entrada, salida o instancias de cualquier otra clase.
// 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
se pasa como el primer argumento al constructor del grupo, el grupo usa sincronización internamente y puede acceder mediante múltiples hilos al mismo tiempo.
Si true
se pasa como el segundo argumento al constructor del grupo, el grupo almacena objetos usando java.lang.ref.softreference. Esto permite que los objetos en la piscina se recolecten basura cuando la presión de memoria sobre el JVM es alta. clean
de la piscina elimina todas las referencias suaves cuyo objeto ha sido recolectado de basura. Esto puede reducir el tamaño de la piscina cuando no se ha establecido una capacidad máxima. Cuando la piscina tiene una capacidad máxima, no es necesario llamar a clean
porque la free
intentará eliminar una referencia vacía si se ha alcanzado la capacidad máxima.
El tercer parámetro del grupo es la capacidad máxima. Si se libera un objeto y el grupo ya contiene el número máximo de objetos libres, el objeto especificado se restablece pero no se agrega al grupo. La capacidad máxima se puede omitir sin límite.
Si un objeto implementa el grupo. Poolable, entonces reset
agrupable se llama cuando se libera el objeto. Esto le da al objeto la oportunidad de restablecer su estado para su reutilización en el futuro. Alternativamente, reset
del grupo se puede anular para restablecer objetos. Implementación de entrada y salida de salida agrupable para establecer su position
y total
en 0. KRYO no implementa agrupable porque su estado de gráfico de objetos generalmente se reinicia automáticamente después de cada serialización (ver reinicio). Si deshabilita el reinicio automático a través de setAutoReset(false)
, asegúrese de llamar a Kryo.reset()
antes de devolver la instancia al grupo.
Pool getFree
Devuelve el número de objetos disponibles que se obtendrán. Si usa referencias suaves, este número puede incluir objetos que se han recolectado basura. clean
se puede usar primero para eliminar referencias suaves vacías.
Pool getPeak
devuelve el mayor número de objetos gratuitos de todos los tiempos. Esto puede ayudar a determinar si la capacidad máxima de un grupo se establece adecuadamente. Se puede restablecer en cualquier momento con resetPeak
.
Kryo proporciona varios puntos de referencia basados en JMH y archivos R/GGPLOT2.
KRYO se puede comparar con muchas otras bibliotecas de serialización en el Proyecto de Serializadores JVM. Los puntos de referencia son pequeños, anticuados y de cosecha propia en lugar de usar JMH, por lo que son menos confiables. Además, es muy difícil comparar a fondo las bibliotecas de serialización utilizando un punto de referencia. Las bibliotecas tienen muchas características diferentes y, a menudo, tienen objetivos diferentes, por lo que pueden sobresalir para resolver problemas completamente diferentes. Para comprender estos puntos de referencia, el código que se ejecuta y los datos que se están siendo serializados deben analizarse y contrastar con sus necesidades específicas. Algunos serializadores están altamente optimizados y usan páginas de código, otros usan solo unas pocas líneas. Es bueno mostrar lo que es posible, pero puede no ser una comparación relevante para muchas situaciones.
Hay una serie de proyectos que usan KRyo. Algunos se enumeran a continuación. Envíe una solicitud de extracción si desea que su proyecto se incluya aquí.