Kryo é uma estrutura de serialização de gráficos de objetos binários rápida e eficiente para Java. Os objetivos do projeto são alta velocidade, tamanho reduzido e uma API fácil de usar. O projeto é útil sempre que os objetos precisam ser persistidos, seja em um arquivo, banco de dados ou pela rede.
Kryo também pode realizar cópias/clonagens profundas e superficiais automáticas. Esta é uma cópia direta de objeto para objeto, não de objeto para bytes para objeto.
Esta documentação é para o Kryo versão 5.x. Veja o Wiki para a versão 4.x.
Por favor, use a lista de discussão Kryo para perguntas, discussões e suporte. Limite o uso do rastreador de problemas Kryo a bugs e melhorias, não a perguntas, discussões ou suporte.
Kryo publica dois tipos de artefatos/frascos:
Os JARs Kryo estão disponíveis na página de lançamentos e no Maven Central. Os snapshots mais recentes do Kryo, incluindo builds de snapshots do master, estão no Repositório Sonatype.
Para usar a versão mais recente do Kryo em seu aplicativo, use esta entrada de dependência em seu pom.xml
:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
Para usar a versão mais recente do Kryo em uma biblioteca que você deseja publicar, use esta entrada de dependência em seu pom.xml
:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
Para usar o instantâneo mais recente do Kryo, use:
< 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 >
Nem todo mundo é fã do Maven. Usar o Kryo sem o Maven requer colocar o JAR do Kryo no seu caminho de classe junto com os JARs de dependência encontrados na lib.
Construir o Kryo a partir do código-fonte requer JDK11+ e Maven. Para construir todos os artefatos, execute:
mvn clean && mvn install
Avançando para mostrar como a biblioteca pode ser usada:
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 ;
}
}
A classe Kryo realiza a serialização automaticamente. As classes Output e Input lidam com bytes de buffer e, opcionalmente, liberação para um fluxo.
O restante deste documento detalha como isso funciona e o uso avançado da biblioteca.
A entrada e saída de dados do Kryo é feita usando as classes Input e Output. Essas classes não são thread-safe.
A classe Output é um OutputStream que grava dados em um buffer de matriz de bytes. Este buffer pode ser obtido e usado diretamente, se uma matriz de bytes for desejada. Se o Output receber um OutputStream, ele liberará os bytes para o fluxo quando o buffer ficar cheio, caso contrário, o Output poderá aumentar seu buffer automaticamente. A saída possui muitos métodos para escrever primitivas e strings em bytes com eficiência. Ele fornece funcionalidades semelhantes a DataOutputStream, BufferedOutputStream, FilterOutputStream e ByteArrayOutputStream, tudo em uma classe.
Dica: Saída e Entrada fornecem todas as funcionalidades do ByteArrayOutputStream. Raramente há um motivo para liberar a saída para um ByteArrayOutputStream.
A saída armazena em buffer os bytes ao gravar em um OutputStream, portanto, flush
ou close
deve ser chamado após a conclusão da gravação para fazer com que os bytes em buffer sejam gravados em OutputStream. Se a saída não tiver sido fornecida como OutputStream, chamar flush
ou close
será desnecessário. Ao contrário de muitos fluxos, uma instância de Output pode ser reutilizada definindo a posição ou definindo uma nova matriz ou fluxo de bytes.
Dica: Como os buffers de saída já possuem buffers, não há razão para liberar a saída para um BufferedOutputStream.
O construtor de saída de argumento zero cria uma saída não inicializada. A saída setBuffer
deve ser chamada antes que a Saída possa ser usada.
A classe Input é um InputStream que lê dados de um buffer de matriz de bytes. Este buffer pode ser definido diretamente, se for desejada a leitura de uma matriz de bytes. Se o Input receber um InputStream, ele preencherá o buffer do fluxo quando todos os dados no buffer forem lidos. A entrada possui muitos métodos para ler com eficiência primitivas e strings de bytes. Ele fornece funcionalidades semelhantes a DataInputStream, BufferedInputStream, FilterInputStream e ByteArrayInputStream, tudo em uma classe.
Dica: Input fornece todas as funcionalidades de ByteArrayInputStream. Raramente há um motivo para que Input seja lido de um ByteArrayInputStream.
Se o Input close
for chamado, o InputStream do Input será fechado, se houver. Se não estiver lendo um InputStream, não será necessário chamar close
. Ao contrário de muitos fluxos, uma instância de Input pode ser reutilizada definindo a posição e o limite ou definindo uma nova matriz de bytes ou InputStream.
O construtor Input de argumento zero cria uma entrada não inicializada. A entrada setBuffer
deve ser chamada antes que a entrada possa ser usada.
As classes ByteBufferOutput e ByteBufferInput funcionam exatamente como Output e Input, exceto que usam um ByteBuffer em vez de uma matriz de bytes.
As classes UnsafeOutput, UnsafeInput, UnsafeByteBufferOutput e UnsafeByteBufferInput funcionam exatamente como suas contrapartes não inseguras, exceto que usam sun.misc.Unsafe para maior desempenho em muitos casos. Para usar essas classes, Util.unsafe
deve ser verdadeiro.
A desvantagem de usar buffers inseguros é que o endianismo nativo e a representação dos tipos numéricos do sistema que executa a serialização afetam os dados serializados. Por exemplo, a desserialização falhará se os dados forem gravados no X86 e lidos no SPARC. Além disso, se os dados forem gravados com um buffer inseguro, eles deverão ser lidos com um buffer inseguro.
A maior diferença de desempenho com buffers inseguros ocorre com grandes matrizes primitivas quando a codificação de comprimento variável não é usada. A codificação de comprimento variável pode ser desabilitada para buffers inseguros ou apenas para campos específicos (ao usar FieldSerializer).
As classes IO fornecem métodos para ler e escrever valores int (varint) e longos (varlong) de comprimento variável. Isso é feito usando o 8º bit de cada byte para indicar se mais bytes seguem, o que significa que um varint usa de 1 a 5 bytes e um varlong usa de 1 a 9 bytes. Usar codificação de comprimento variável é mais caro, mas torna os dados serializados muito menores.
Ao escrever um valor de comprimento variável, o valor pode ser otimizado para valores positivos ou para valores negativos e positivos. Por exemplo, quando otimizado para valores positivos, 0 a 127 é escrito em um byte, 128 a 16383 em dois bytes, etc. No entanto, pequenos números negativos são o pior caso em 5 bytes. Quando não otimizados para positivo, esses intervalos são reduzidos pela metade. Por exemplo, -64 a 63 é escrito em um byte, 64 a 8191 e -65 a -8192 em dois bytes, etc.
Os buffers de entrada e saída fornecem métodos para ler e gravar valores de tamanho fixo ou comprimento variável. Existem também métodos para permitir que o buffer decida se um valor de tamanho fixo ou de comprimento variável será gravado. Isso permite que o código de serialização garanta que a codificação de comprimento variável seja usada para valores muito comuns que aumentariam a saída se um tamanho fixo fosse usado, ao mesmo tempo que permite que a configuração do buffer decida todos os outros valores.
Método | Descrição |
---|---|
escrevaInt(int) | Grava um int de 4 bytes. |
writeVarInt(int, booleano) | Grava um int de 1 a 5 bytes. |
writeInt(int, booleano) | Grava um int de 4 ou 1-5 bytes (o buffer decide). |
escreverLong(longo) | Grava um comprimento de 8 bytes. |
writeVarLong(longo, booleano) | Grava um byte de 1 a 9. |
writeLong(longo, booleano) | Grava um byte de 8 ou 1-9 (o buffer decide). |
Para desabilitar a codificação de comprimento variável para todos os valores, os métodos writeVarInt
, writeVarLong
, readVarInt
e readVarLong
precisariam ser substituídos.
Pode ser útil escrever o comprimento de alguns dados e depois os dados. Quando o comprimento dos dados não é conhecido antecipadamente, todos os dados precisam ser armazenados em buffer para determinar seu comprimento, então o comprimento pode ser gravado e, em seguida, os dados. usar um buffer único e grande para isso impediria o streaming e poderia exigir um buffer excessivamente grande, o que não é o ideal.
A codificação fragmentada resolve esse problema usando um buffer pequeno. Quando o buffer está cheio, seu comprimento é gravado e depois os dados. Este é um pedaço de dados. O buffer é limpo e isso continua até que não haja mais dados para gravar. Um pedaço com comprimento zero indica o fim dos pedaços.
Kryo fornece classes para facilitar o uso da codificação em partes. OutputChunked é usado para gravar dados em partes. Ele estende a saída, assim como todos os métodos convenientes para gravar dados. Quando o buffer OutputChunked está cheio, ele libera o pedaço para outro OutputStream. O método endChunk
é usado para marcar o final de um conjunto de pedaços.
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 ler os dados fragmentados, InputChunked é usado. Ele estende a entrada, assim como todos os métodos convenientes para ler dados. Ao ler, InputChunked parecerá atingir o final dos dados quando atingir o final de um conjunto de blocos. O método nextChunks
avança para o próximo conjunto de blocos, mesmo que nem todos os dados tenham sido lidos do conjunto atual de blocos.
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 ();
Geralmente a saída e a entrada fornecem bom desempenho. Buffers inseguros têm desempenho tão bom ou melhor, especialmente para arrays primitivos, se suas incompatibilidades entre plataformas forem aceitáveis. ByteBufferOutput e ByteBufferInput fornecem desempenho um pouco pior, mas isso pode ser aceitável se o destino final dos bytes for um ByteBuffer.
A codificação de comprimento variável é mais lenta do que valores fixos, especialmente quando há muitos dados sendo utilizados.
A codificação fragmentada usa um buffer intermediário para adicionar uma cópia adicional de todos os bytes. Isso por si só pode ser aceitável, no entanto, quando usado em um serializador reentrante, o serializador deve criar um OutputChunked ou InputChunked para cada objeto. A alocação e a coleta de lixo desses buffers durante a serialização podem ter um impacto negativo no desempenho.
Kryo possui três conjuntos de métodos para ler e escrever objetos. Se a classe concreta do objeto não for conhecida e o objeto puder ser nulo:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
Se a classe for conhecida e o objeto puder ser nulo:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
Se a classe for conhecida e o objeto não puder ser nulo:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
Todos esses métodos primeiro encontram o serializador apropriado para usar e, em seguida, usam-no para serializar ou desserializar o objeto. Os serializadores podem chamar esses métodos para serialização recursiva. Múltiplas referências ao mesmo objeto e referências circulares são tratadas automaticamente pelo Kryo.
Além de métodos para ler e escrever objetos, a classe Kryo fornece uma maneira de registrar serializadores, ler e gravar identificadores de classe de forma eficiente, manipular objetos nulos para serializadores que não podem aceitar nulos e manipular referências de objetos de leitura e gravação (se habilitado). Isso permite que os serializadores se concentrem em suas tarefas de serialização.
Ao testar e explorar APIs Kryo, pode ser útil gravar um objeto em bytes e, em seguida, ler esses bytes de volta em um 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 );
Neste exemplo, a Saída começa com um buffer com capacidade de 1024 bytes. Se mais bytes forem gravados na Saída, o tamanho do buffer aumentará sem limite. A Saída não precisa ser fechada porque não recebeu um OutputStream. A entrada lê diretamente do buffer byte[]
da saída.
Kryo suporta fazer cópias profundas e superficiais de objetos usando atribuição direta de um objeto para outro. Isso é mais eficiente do que serializar em bytes e retornar a objetos.
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
Todos os serializadores usados precisam suportar cópia. Todos os serializadores fornecidos com Kryo suportam cópia.
Assim como na serialização, ao copiar, múltiplas referências ao mesmo objeto e referências circulares são tratadas automaticamente pelo Kryo se as referências estiverem habilitadas.
Se usar o Kryo apenas para copiar, o registro pode ser desativado com segurança.
Kryo getOriginalToCopyMap
pode ser usado depois que um gráfico de objeto é copiado para obter um mapa de objetos antigos para novos. O mapa é limpo automaticamente pelo Kryo reset
, portanto só é útil quando Kryo setAutoReset
é falso.
Por padrão, as referências não estão habilitadas. Isso significa que se um objeto aparecer várias vezes em um gráfico de objetos, ele será escrito várias vezes e será desserializado como vários objetos diferentes. Quando as referências são desativadas, as referências circulares causarão falha na serialização. As referências são habilitadas ou desabilitadas com Kryo setReferences
para serialização e setCopyReferences
para cópia.
Quando as referências estão habilitadas, um varint é escrito antes de cada objeto na primeira vez que ele aparece no gráfico do objeto. Para aparições subsequentes dessa classe no mesmo gráfico de objeto, apenas um varint é escrito. Após a desserialização, as referências de objetos são restauradas, incluindo quaisquer referências circulares. Os serializadores em uso devem suportar referências chamando reference
Kryo em Serializer read
.
A ativação de referências afeta o desempenho porque cada objeto lido ou gravado precisa ser rastreado.
Nos bastidores, um ReferenceResolver lida com objetos de rastreamento que foram lidos ou gravados e fornece IDs de referência internos. Várias implementações são fornecidas:
ReferenceResolver useReferences(Class)
pode ser substituído. Ele retorna um booleano para decidir se as referências são suportadas para uma classe. Se uma classe não suportar referências, o ID de referência varint não será escrito antes dos objetos desse tipo. Se uma classe não precisar de referências e objetos desse tipo aparecerem no gráfico de objetos muitas vezes, o tamanho serializado poderá ser bastante reduzido desativando as referências para essa classe. O resolvedor de referência padrão retorna false para todos os wrappers e enums primitivos. É comum também retornar false para String e outras classes, dependendo dos gráficos do objeto que estão sendo serializados.
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
O resolvedor de referência determina o número máximo de referências em um gráfico de objeto único. Os índices de array Java são limitados a Integer.MAX_VALUE
, portanto, resolvedores de referência que usam estruturas de dados baseadas em arrays podem resultar em java.lang.NegativeArraySizeException
ao serializar mais de aproximadamente 2 bilhões de objetos. Kryo usa IDs de classe int, portanto, o número máximo de referências em um gráfico de objeto único é limitado a toda a gama de números positivos e negativos em um int (~4 bilhões).
Kryo getContext
retorna um mapa para armazenar dados do usuário. A instância Kryo está disponível para todos os serializadores, portanto, esses dados são facilmente acessíveis a todos os serializadores.
Kryo getGraphContext
é semelhante, mas é limpo após cada gráfico de objeto ser serializado ou desserializado. Isso facilita o gerenciamento do estado que é relevante apenas para o gráfico do objeto atual. Por exemplo, isso pode ser usado para escrever alguns dados de esquema na primeira vez que uma classe é encontrada em um gráfico de objeto. Consulte CompatívelFieldSerializer para obter um exemplo.
Por padrão, reset
do Kryo é chamada após a serialização de cada gráfico de objeto inteiro. Isso redefine nomes de classes não registrados no resolvedor de classe, referências a objetos previamente serializados ou desserializados no resolvedor de referência e limpa o contexto do gráfico. Kryo setAutoReset(false)
pode ser usado para desativar a chamada de reset
automaticamente, permitindo que esse estado abranja vários gráficos de objetos.
Kryo é uma estrutura para facilitar a serialização. A estrutura em si não impõe um esquema nem se importa com o que ou como os dados são gravados ou lidos. Os serializadores são conectáveis e tomam decisões sobre o que ler e escrever. Muitos serializadores são fornecidos prontos para uso para ler e gravar dados de várias maneiras. Embora os serializadores fornecidos possam ler e gravar a maioria dos objetos, eles podem ser facilmente substituídos parcial ou totalmente por seus próprios serializadores.
Quando Kryo escreve uma instância de um objeto, primeiro pode ser necessário escrever algo que identifique a classe do objeto. Por padrão, todas as classes que o Kryo irá ler ou escrever devem ser registradas previamente. O registro fornece um ID de classe int, o serializador a ser usado para a classe e o instanciador de objeto usado para criar instâncias da classe.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Durante a desserialização, as classes registradas devem ter exatamente os mesmos IDs que tinham durante a serialização. Quando registrada, uma classe recebe o próximo ID inteiro mais baixo disponível, o que significa que as classes de ordem são registradas é importante. O ID da classe pode opcionalmente ser especificado explicitamente para tornar o pedido sem importância:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
Os IDs de classe -1 e -2 são reservados. Os IDs de classe 0 a 8 são usados por padrão para tipos primitivos e String, embora esses IDs possam ser reaproveitados. Os IDs são escritos como variantes otimizadas positivas, portanto são mais eficientes quando são números inteiros pequenos e positivos. IDs negativos não são serializados de forma eficiente.
Nos bastidores, um ClassResolver lida com a leitura e gravação de bytes para representar uma classe. A implementação padrão é suficiente na maioria dos casos, mas pode ser substituída para personalizar o que acontece quando uma classe é registrada, o que acontece quando uma classe não registrada é encontrada durante a serialização e o que é lido e gravado para representar uma classe.
O Kryo pode ser configurado para permitir a serialização sem registrar classes antecipadamente.
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
O uso de turmas registradas e não registradas pode ser misto. Classes não registradas têm duas desvantagens principais:
Se usar o Kryo apenas para copiar, o registro pode ser desativado com segurança.
Quando o registro não é necessário, Kryo setWarnUnregisteredClasses
pode ser habilitado para registrar uma mensagem quando uma classe não registrada for encontrada. Isso pode ser usado para obter facilmente uma lista de todas as turmas não registradas. Kryo unregisteredClassMessage
pode ser substituído para personalizar a mensagem de log ou realizar outras ações.
Quando uma classe é registrada, uma instância do serializador pode ser opcionalmente especificada. Durante a desserialização, as classes registradas devem ter exatamente os mesmos serializadores e configurações de serializador que tinham durante a serialização.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
Se um serializador não for especificado ou quando uma classe não registrada for encontrada, um serializador será escolhido automaticamente em uma lista de "serializadores padrão" que mapeia uma classe para um serializador. Ter muitos serializadores padrão não afeta o desempenho da serialização, portanto, por padrão, o Kryo possui mais de 50 serializadores padrão para várias classes JRE. Serializadores padrão adicionais podem ser adicionados:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Isso fará com que uma instância SomeSerializer seja criada quando SomeClass ou qualquer classe que estenda ou implemente SomeClass for registrada.
Os serializadores padrão são classificados para que classes mais específicas sejam correspondidas primeiro, mas caso contrário, são correspondidas na ordem em que são adicionadas. A ordem em que são adicionados pode ser relevante para interfaces.
Se nenhum serializador padrão corresponder a uma classe, o serializador padrão global será usado. O serializador padrão global é definido como FieldSerializer por padrão, mas pode ser alterado. Normalmente, o serializador global é aquele que pode lidar com muitos tipos diferentes.
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
Com este código, assumindo que nenhum serializador padrão corresponda a SomeClass, TaggedFieldSerializer será usado.
Uma classe também pode usar a anotação DefaultSerializer, que será usada em vez de escolher um dos serializadores padrão do Kryo:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
Para máxima flexibilidade, Kryo getDefaultSerializer
pode ser substituído para implementar lógica personalizada para escolher e instanciar um serializador.
O método addDefaultSerializer(Class, Class)
não permite a configuração do serializador. Uma fábrica de serializador pode ser definida em vez de uma classe de serializador, permitindo que a fábrica crie e configure cada instância do serializador. As fábricas são fornecidas para serializadores comuns, geralmente com um método getConfig
para configurar os serializadores criados.
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 );
A fábrica do serializador possui um método isSupported(Class)
que permite recusar o tratamento de uma classe, mesmo que de outra forma corresponda à classe. Isso permite que uma fábrica verifique múltiplas interfaces ou implemente outra lógica.
Embora alguns serializadores sejam para uma classe específica, outros podem serializar muitas classes diferentes. Os serializadores podem usar Kryo newInstance(Class)
para criar uma instância de qualquer classe. Isso é feito procurando o registro da classe e, em seguida, usando o ObjectInstantiator do registro. O instanciador pode ser especificado no registro.
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Caso o registro não possua instanciador, um é fornecido pelo Kryo newInstantiator
. Para personalizar como os objetos são criados, Kryo newInstantiator
pode ser substituído ou um InstantiatorStrategy fornecido.
Kryo fornece DefaultInstantiatorStrategy que cria objetos usando ReflectASM para chamar um construtor de argumento zero. Se isso não for possível, ele usa reflexão para chamar um construtor de argumento zero. Se isso também falhar, ele lançará uma exceção ou tentará um InstantiatorStrategy substituto. O Reflection usa setAccessible
, portanto, um construtor privado de argumento zero pode ser uma boa maneira de permitir que Kryo crie instâncias de uma classe sem afetar a API pública.
DefaultInstantiatorStrategy é a forma recomendada de criar objetos com Kryo. Ele executa construtores exatamente como seria feito com código Java. Mecanismos extralinguísticos alternativos também podem ser usados para criar objetos. O Objenesis StdInstantiatorStrategy usa APIs específicas da JVM para criar uma instância de uma classe sem chamar nenhum construtor. Usar isso é perigoso porque a maioria das classes espera que seus construtores sejam chamados. Criar o objeto ignorando seus construtores pode deixar o objeto em um estado não inicializado ou inválido. As classes devem ser projetadas para serem criadas dessa maneira.
O Kryo pode ser configurado para testar primeiro o DefaultInstantiatorStrategy e, em seguida, retornar ao StdInstantiatorStrategy, se necessário.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
Outra opção é SerializingInstantiatorStrategy, que usa o mecanismo de serialização integrado do Java para criar uma instância. Usando isso, a classe deve implementar java.io.Serializable e o primeiro construtor de argumento zero em uma superclasse é invocado. Isso também ignora construtores e, portanto, é perigoso pelos mesmos motivos que StdInstantiatorStrategy.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
Alternativamente, alguns serializadores genéricos fornecem métodos que podem ser substituídos para personalizar a criação de objetos para um tipo específico, em vez de chamar 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 );
}
});
Alguns serializadores fornecem um método writeHeader
que pode ser substituído para gravar os dados necessários na create
no momento certo.
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 ));
}
}
Se um serializador não fornecer writeHeader
, a gravação de dados para create
poderá ser feita em 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 ());
}
}
Mesmo quando um serializador conhece a classe esperada para um valor (por exemplo, a classe de um campo), se a classe concreta do valor não for final, então o serializador precisa primeiro escrever o ID da classe e depois o valor. As classes finais podem ser serializadas com mais eficiência porque não são polimórficas.
Kryo isFinal
é usado para determinar se uma aula é final. Este método pode ser substituído para retornar verdadeiro mesmo para tipos que não são finais. Por exemplo, se um aplicativo usa ArrayList extensivamente, mas nunca usa uma subclasse ArrayList, tratar ArrayList como final pode permitir que FieldSerializer economize de 1 a 2 bytes por campo ArrayList.
Kryo pode serializar fechamentos Java 8+ que implementam java.io.Serializable, com algumas ressalvas. Os encerramentos serializados em uma JVM podem falhar ao serem desserializados em uma JVM diferente.
Kryo isClosure
é usado para determinar se uma classe é um encerramento. Nesse caso, ClosureSerializer.Closure será usado para localizar o registro da classe em vez da classe do encerramento. Para serializar encerramentos, as seguintes classes devem ser registradas: ClosureSerializer.Closure, Object[] e Class. Além disso, a classe de captura do fechamento deve ser cadastrada.
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 );
A serialização de encerramentos que não implementam Serializable é possível com algum esforço.
Kryo suporta streams, por isso é trivial usar compactação ou criptografia em todos os 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 ();
Se necessário, um serializador pode ser usado para compactar ou criptografar os bytes apenas para um subconjunto de bytes de um gráfico de objeto. Por exemplo, consulte DeflateSerializer ou BlowfishSerializer. Esses serializadores envolvem outro serializador para codificar e decodificar os bytes.
A classe abstrata Serializer define métodos para ir de objetos a bytes e 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 ());
}
}
O serializador possui apenas dois métodos que devem ser implementados. write
grava o objeto como bytes na saída. read
cria uma nova instância do objeto e lê a entrada para preenchê-la.
Quando Kryo é usado para ler um objeto aninhado na read
do Serializer, reference
Kryo deve primeiro ser chamada com o objeto pai se for possível que o objeto aninhado faça referência ao objeto pai. É desnecessário chamar reference
Kryo se os objetos aninhados não puderem fazer referência ao objeto pai, se o Kryo não estiver sendo usado para objetos aninhados ou se as referências não estiverem sendo usadas. Se objetos aninhados puderem usar o mesmo serializador, o serializador deverá 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 ;
}
Os serializadores geralmente não devem fazer uso direto de outros serializadores; em vez disso, os métodos de leitura e gravação do Kryo devem ser usados. Isso permite que Kryo orquestre a serialização e lide com recursos como referências e objetos nulos. Às vezes, um serializador sabe qual serializador usar para um objeto aninhado. Nesse caso, deve usar os métodos de leitura e escrita do Kryo que aceitam um serializador.
Se o objeto pudesse ser nulo:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
Se o objeto não puder ser nulo:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
Durante a serialização, o Kryo getDepth
fornece a profundidade atual do gráfico do objeto.
Quando uma serialização falha, uma KryoException pode ser lançada com informações de rastreamento de serialização sobre onde ocorreu a exceção no gráfico do objeto. Ao usar serializadores aninhados, KryoException pode ser capturado para adicionar informações de rastreamento de serialização.
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 ;
}
}
Os serializadores fornecidos pelo Kryo usam a pilha de chamadas ao serializar objetos aninhados. Kryo minimiza chamadas de pilha, mas pode ocorrer um estouro de pilha para gráficos de objetos extremamente profundos. Este é um problema comum para a maioria das bibliotecas de serialização, incluindo a serialização Java integrada. O tamanho da pilha pode ser aumentado usando -Xss
, mas observe que isso se aplica a todos os threads. Tamanhos grandes de pilha em uma JVM com muitos threads podem usar uma grande quantidade de memória.
Kryo setMaxDepth
pode ser usado para limitar a profundidade máxima de um gráfico de objeto. Isso pode evitar que dados maliciosos causem um estouro de pilha.
Por padrão, os serializadores nunca receberão um nulo; em vez disso, o Kryo escreverá um byte conforme necessário para denotar nulo ou não nulo. Se um serializador puder ser mais eficiente manipulando os próprios nulos, ele poderá chamar Serializer setAcceptsNull(true)
. Isso também pode ser usado para evitar a gravação do byte que denota nulo quando se sabe que todas as instâncias que o serializador irá manipular nunca serão nulas.
Kryo getGenerics
fornece informações genéricas de tipo para que os serializadores possam ser mais eficientes. Isso é mais comumente usado para evitar escrever a classe quando o parâmetro de tipo class é final.
A inferência de tipo genérico é habilitada por padrão e pode ser desabilitada com Kryo setOptimizedGenerics(false)
. Desabilitar a otimização genérica pode aumentar o desempenho ao custo de um tamanho serializado maior.
Se a classe tiver um único parâmetro de tipo, nextGenericClass
retornará o parâmetro de tipo class ou null se nenhum. Após ler ou gravar qualquer objeto aninhado, popGenericType
deve ser chamado. Consulte CollectionSerializer para obter um exemplo.
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 uma classe com vários parâmetros de tipo, nextGenericTypes
retorna uma matriz de instâncias de GenericType e resolve
é usado para obter a classe para cada GenericType. Após ler ou gravar qualquer objeto aninhado, popGenericType
deve ser chamado. Consulte MapSerializer para obter um exemplo.
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 serializadores que passam informações de parâmetros de tipo para objetos aninhados no gráfico de objetos (uso um tanto avançado), primeiro GenericsHierarchy é usado para armazenar os parâmetros de tipo para uma classe. Durante a serialização, Generics pushTypeVariables
é chamado antes que os tipos genéricos sejam resolvidos (se houver). Se >0 for retornado, isso deverá ser seguido por Generics popTypeVariables
. Consulte FieldSerializer para obter um exemplo.
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 ;
}
}
Em vez de usar um serializador, uma classe pode optar por fazer sua própria serialização implementando KryoSerializable (semelhante 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, a instância já deve ter sido criada antes que read
possa ser chamada, portanto a classe não é capaz de controlar sua própria criação. Uma classe KryoSerializable usará o serializador padrão KryoSerializableSerializer, que usa Kryo newInstance
para criar uma nova instância. É trivial escrever seu próprio serializador para personalizar o processo, chamar métodos antes ou depois da serialização, etc.
Os serializadores só suportam cópia se copy
for substituída. Semelhante ao Serializer read
, este método contém a lógica para criar e configurar a cópia. Assim como read
, reference
Kryo deve ser chamada antes que Kryo seja usado para copiar objetos filhos, se algum dos objetos filhos puder fazer referência ao objeto pai.
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 ;
}
}
Em vez de usar um serializador, as classes podem implementar KryoCopyable para fazer suas próprias cópias:
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 ;
}
}
O serializador setImmutable(true)
pode ser usado quando o tipo é imutável. Nesse caso, copy
do Serializer não precisa ser implementada – a implementação copy
padrão retornará o objeto original.
As seguintes regras básicas são aplicadas à numeração de versão do Kryo:
A atualização de qualquer dependência é um evento significativo, mas uma biblioteca de serialização é mais propensa a quebrar do que a maioria das dependências. Ao atualizar o Kryo, verifique as diferenças da versão e teste a nova versão em seus próprios aplicativos. Tentamos torná -lo o mais seguro e fácil possível.
Os serializadores de Kryo fornecidos por padrão assumem que o Java será usado para deserialização, para que não definam explicitamente o formato que está escrito. Os serializadores podem ser gravados usando um formato padronizado que é mais facilmente lido por outros idiomas, mas isso não é fornecido por padrão.
Para algumas necessidades, como armazenamento de longo prazo de bytes serializados, pode ser importante como a serialização lida com as alterações nas classes. Isso é conhecido como compatibilidade direta (leitura de bytes serializados por classes mais recentes) e compatibilidade com versões anteriores (lendo bytes serializados por classes mais antigas). O Kryo fornece alguns serializadores genéricos que adotam abordagens diferentes para lidar com a compatibilidade. Serializadores adicionais podem ser facilmente desenvolvidos para compatibilidade para a frente e para trás, como um serializador que usa um esquema externo e escrito à mão.
Quando uma classe muda mais do que o seu serializador pode lidar, um serializador pode ser gravado para transferir os dados para uma classe diferente. Todo o uso da classe antiga no código do aplicativo deve ser substituído pela nova classe. A classe antiga é mantida apenas 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 );
O Kryo fornece a muitos serializadores várias opções de configuração e níveis de compatibilidade. Serializadores adicionais podem ser encontrados no Projeto Sister Kryo-Serializers, que hospeda serializadores que acessam APIs privadas ou não são perfeitamente seguras em todas as JVMs. Mais serializadores podem ser encontrados na seção Links.
O FieldSerializer funciona serializando cada campo não transitório. Ele pode serializar Pojos e muitas outras classes sem nenhuma configuração. Todos os campos não públicos são escritos e lidos por padrão, por isso é importante avaliar cada classe que será serializada. Se os campos forem públicos, a serialização pode ser mais rápida.
O FieldSerializer é eficiente, escrevendo apenas os dados do campo, sem nenhuma informação do esquema, usando os arquivos da classe Java como esquema. Não suporta adicionar, remover ou alterar o tipo de campos sem invalidar bytes serializados anteriormente. A renomeação dos campos é permitida apenas se não alterar a ordem alfabética dos campos.
As desvantagens de compatibilidade do FieldSerializer podem ser aceitáveis em muitas situações, como ao enviar dados em uma rede, mas podem não ser uma boa opção para armazenamento de dados a longo prazo, porque as classes Java não podem evoluir. Em muitos casos, o TaggedFieldSerializer é uma escolha melhor.
Contexto | Descrição | Valor padrão |
---|---|---|
fieldsCanBeNull | Quando falso, assume-se que nenhum valores de campo é nulo, que pode economizar 0-1 byte por campo. | verdadeiro |
setFieldsAsAccessible | Quando verdadeiro, todos os campos não transitórios (incluindo campos privados) serão serializados e setAccessible , se necessário. Se falso, apenas campos na API público serão serializados. | verdadeiro |
ignoreSyntheticFields | Se verdadeiro, os campos sintéticos (gerados pelo compilador para escopo) são serializados. | falso |
fixedFieldTypes | Se verdadeiro, é assumido que o tipo de concreto de todo valor de campo corresponde ao tipo de campo. Isso remove a necessidade de escrever o ID da classe para valores de campo. | falso |
copyTransient | Se for verdade, todos os campos transitórios serão copiados. | verdadeiro |
serializeTransient | Se verdadeiro, campos transitórios serão serializados. | falso |
variableLengthEncoding | Se verdadeiro, os valores de comprimento variável são usados para campos int e longos. | verdadeiro |
extendedFieldNames | Se verdadeiro, os nomes de campo são prefixados por sua classe de declaração. Isso pode evitar conflitos quando uma subclasse tem um campo com o mesmo nome que uma super classe. | falso |
O FieldSerializer fornece os campos que serão serializados. Os campos podem ser removidos, para que não sejam serializados. Os campos podem ser configurados para tornar a seriação mais 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 ());
Contexto | Descrição | Valor padrão |
---|---|---|
canBeNull | Quando false, é assumido que o valor do campo nunca é nulo, que pode salvar 0-1 byte. | verdadeiro |
valueClass | Define a classe de concreto e o serializador para usar para o valor do campo. Isso remove a necessidade de escrever o ID da classe para o valor. Se a classe do valor do campo for um invólucro primitivo, primitivo ou final, essa configuração padrão da classe do campo. | nulo |
serializer | Define o serializador a ser usado para o valor do campo. Se o serializador estiver definido, alguns serializadores exigiram que a classe de valor também fosse definida. Se NULL, o serializador registrado no KRYO para a classe do valor do campo será usado. | nulo |
variableLengthEncoding | Se verdadeiro, valores de comprimento variáveis são usados. Isso se aplica apenas a campos int ou longos. | verdadeiro |
optimizePositive | Se verdadeiro, valores positivos são otimizados para valores de comprimento variáveis. Isso se aplica apenas a campos int ou longos quando a codificação de comprimento variável é usada. | verdadeiro |
As anotações podem ser usadas para configurar os serializadores para cada campo.
Anotação | Descrição |
---|---|
@Bind | Define as configurações de campo em cache para qualquer campo. |
@CollectionBind | Define as configurações do coleção de correção para os campos de coleta. |
@MapBind | Define as configurações do MapSerializer para campos de mapa. |
@NotNull | Marca um campo como nunca sendo 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 estende o FieldSerializer e fornece compatibilidade com versões anteriores. Isso significa que os campos podem ser adicionados sem invalidar bytes serializados anteriormente. Remover, renomear ou alterar o tipo de campo não é suportado.
Quando um campo é adicionado, ele deve ter a anotação @Since(int)
para indicar a versão em que foi adicionada para ser compatível com bytes serializados anteriormente. O valor da anotação nunca deve mudar.
VersionFieldSerializer adiciona muito pouco sobrecarga ao FieldSerializer: um único VarInt adicional.
Contexto | Descrição | Valor padrão |
---|---|---|
compatible | Quando falsa, uma exceção é lançada ao ler um objeto com uma versão diferente. A versão de um objeto é a versão máxima de qualquer campo. | verdadeiro |
VersionFieldSerializer também herda todas as configurações do FieldSerializer.
O TaggedFieldSerializer estende o FieldSerializer para fornecer compatibilidade com versões anteriores e compatibilidade opcional para a frente. Isso significa que os campos podem ser adicionados ou renomeados e opcionalmente removidos sem invalidar bytes serializados anteriormente. Alterar o tipo de campo não é suportado.
Somente os campos que possuem uma anotação @Tag(int)
são serializados. Os valores de tag de campo devem ser únicos, tanto dentro de uma classe quanto em todas as suas super classes. Uma exceção é lançada se os valores de tag duplicados forem encontrados.
O desempenho de compatibilidade e serialização para a frente e para trás depende das configurações readUnknownTagData
e chunkedEncoding
. Além disso, um Varint é gravado antes de cada campo para o valor da tag.
Quando readUnknownTagData
e chunkedEncoding
são falsos, os campos não devem ser removidos, mas a anotação @Deprecated
pode ser aplicada. Os campos depreciados são lidos ao ler bytes antigos, mas não são gravados em novos bytes. As aulas podem evoluir lendo os valores dos campos depreciados e escrevê -los em outros lugares. Os campos podem ser renomeados e/ou privados para reduzir a desordem na classe (por exemplo, ignored1
, ignored2
).
TaggedFieldSerializer (com readUnknownTagData
e Falso chunkedEncoding
) é o serializador sugerido para a maioria das classes onde os campos podem ser anotados. Ele permite que as classes evoluam e os campos sejam removidos dos dados serializados (via depreciação), atendendo às necessidades da maioria dos aplicativos sem adicionar muito ao tamanho serializado.
Contexto | Descrição | Valor padrão |
---|---|---|
readUnknownTagData | Quando uma etiqueta falsa e desconhecida é encontrada, uma exceção é lançada ou, se chunkedEncoding for verdadeiro, os dados serão ignorados.Quando é verdade, a classe para cada valor de campo é gravada antes do valor. Quando uma tag desconhecida é encontrada, uma tentativa de ler os dados é feita. Isso é usado para pular os dados e, se as referências forem ativadas, quaisquer outros valores no gráfico de objetos que referenciam que os dados ainda podem ser desserializados. Se a leitura dos dados falhar (por exemplo, a classe é desconhecida ou foi removida), uma exceção será lançada ou, se chunkedEncoding for verdadeiro, os dados serão ignorados.Em ambos os casos, se os dados forem ignorados e as referências estiverem ativadas, quaisquer referências nos dados ignoradas não serão lidas e a desserialização adicional poderá receber as referências erradas e falhar. | falso |
chunkedEncoding | Quando verdadeiro, os campos são escritos com codificação em chunked para permitir que dados de campo desconhecidos sejam ignorados. Isso afeta o desempenho. | falso |
chunkSize | O tamanho máximo de cada pedaço para a codificação em pedaços. | 1024 |
TaggedFieldSerializer também herda todas as configurações do FieldSerializer.
O CompatibleFieldSerializer estende o FieldSerializer para fornecer compatibilidade para a frente e para trás. Isso significa que os campos podem ser adicionados ou removidos sem invalidar bytes serializados anteriormente. Renomear ou alterar o tipo de campo não é suportado. Como o FieldSerializer, ele pode serializar a maioria das classes sem precisar de anotações.
O desempenho de compatibilidade e serialização para a frente e para trás depende das configurações readUnknownFieldData
e chunkedEncoding
. Além disso, a primeira vez que a classe é encontrada nos bytes serializados, um esquema simples é escrito contendo as seqüências de nome de campo. Como os dados do campo são identificados pelo nome, se uma super classe tiver um campo com o mesmo nome como uma subclasse, extendedFieldNames
devem ser verdadeiros.
Contexto | Descrição | Valor padrão |
---|---|---|
readUnknownFieldData | Quando um campo falso e um desconhecido é encontrado, uma exceção é lançada ou, se chunkedEncoding for verdadeiro, os dados serão ignorados.Quando é verdade, a classe para cada valor de campo é gravada antes do valor. Quando um campo desconhecido é encontrado, uma tentativa de ler os dados é feita. Isso é usado para pular os dados e, se as referências forem ativadas, quaisquer outros valores no gráfico de objetos que referenciam que os dados ainda podem ser desserializados. Se a leitura dos dados falhar (por exemplo, a classe é desconhecida ou foi removida), uma exceção será lançada ou, se chunkedEncoding for verdadeira, os dados serão ignorados.Em ambos os casos, se os dados forem ignorados e as referências estiverem ativadas, quaisquer referências nos dados ignoradas não serão lidas e a desserialização adicional poderá receber as referências erradas e falhar. | verdadeiro |
chunkedEncoding | Quando verdadeiro, os campos são escritos com codificação em chunked para permitir que dados de campo desconhecidos sejam ignorados. Isso afeta o desempenho. | falso |
chunkSize | O tamanho máximo de cada pedaço para a codificação em pedaços. | 1024 |
CompatibleFieldSerializer também herda todas as configurações do FieldSerializer.
O Beanserializer é muito semelhante ao FieldSerializer, exceto que usa métodos de feijão Getter e Setter, em vez do acesso direto em campo. Isso um pouco mais lento, mas pode ser mais seguro porque usa a API pública para configurar o objeto. Como o FieldSerializer, ele não fornece compatibilidade para frente ou para trás.
O ColeçãoRializer serializa objetos que implementam a interface java.util.Collection.
Contexto | Descrição | Valor padrão |
---|---|---|
elementsCanBeNull | Quando falso, assume-se que nenhum elemento da coleção é nulo, que pode economizar 0-1 byte por elemento. | verdadeiro |
elementClass | Define a classe de concreto a ser usada para cada elemento da coleção. Isso remove a necessidade de escrever o ID da classe para cada elemento. Se a classe de elementos for conhecida (por exemplo, genéricos) e um invólucro primitivo, primitivo ou final, o CollectionSerializer não escreverá o ID da classe, mesmo quando essa configuração for nula. | nulo |
elementSerializer | Define o serializador a ser usado para todos os elementos da coleção. Se o serializador estiver definido, alguns serializadores exigiram que a classe de valor também fosse definida. Se NULL, o serializador registrado no Kryo para a classe de cada elemento será usado. | nulo |
O MapSerializer serializa objetos que implementam a interface java.util.map.
Contexto | Descrição | Valor padrão |
---|---|---|
keysCanBeNull | Quando false, assume-se que nenhuma chaves no mapa é nula, que pode salvar 0-1 byte por entrada. | verdadeiro |
valuesCanBeNull | Quando false, assume-se que nenhum valores no mapa são nulos, que podem economizar 0-1 byte por entrada. | verdadeiro |
keyClass | Define a classe de concreto a ser usada para cada chave no mapa. Isso remove a necessidade de escrever o ID da classe para cada tecla. | nulo |
valueClass | Define a classe de concreto a ser usada para todos os valores no mapa. Isso remove a necessidade de escrever o ID da classe para cada valor. | nulo |
keySerializer | Define o serializador a ser usado para cada chave no mapa. Se o serializador de valor estiver definido, alguns serializadores exigiram que a classe de valor também fosse definida. Se NULL, o serializador registrado no KRYO para a classe de cada chave será usado. | nulo |
valueSerializer | Define o serializador a ser usado para todos os valores no mapa. Se o serializador principal estiver definido, alguns serializadores exigiram que a classe de valor também fosse definida. Se NULL, o serializador registrado no KRYO para a classe de cada valor será usado. | nulo |
Javaserializer e externalizableSerializer são serializadores de kryo que usam a serialização interna de Java. Isso é tão lento quanto a serialização de Java, mas pode ser necessária para as aulas herdadas.
java.io.externizable e java.io.Serializable não possuem serializadores padrão definidos por padrão; portanto, os serializadores padrão devem ser definidos manualmente ou os serializadores definidos quando a classe é 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 ());
O Kryo faz uso da biblioteca de log de menores de ponta baixa e de Minlog. O nível de registro pode ser definido por um dos seguintes métodos:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo não faz log no INFO
(o padrão) e acima dos níveis. DEBUG
é conveniente de usar durante o desenvolvimento. TRACE
é bom de usar ao depurar um problema específico, mas geralmente gera muita informação para sair.
O Minlog suporta um nível de registro fixo, que faz com que o compilador Java remova as instruções de registro abaixo desse nível no tempo de compilação. O Kryo deve ser compilado com um frasco de minlog de nível de log fixo.
Kryo não é seguro. Cada encadeamento deve ter suas próprias instâncias de Kryo, entrada e saída.
Como o kryo não é seguro e construindo e configurando uma instância de kryo é relativamente caro, em um ambiente de ambiente multithread ou agrupamento pode ser considerado.
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 o pool, o Kryo fornece a classe de piscina que pode agrupar o kryo, a entrada, a saída ou as instâncias de qualquer outra 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 );
Se true
for passado como o primeiro argumento para o construtor de pool, o pool usa sincronização internamente e pode ser acessado por vários threads simultaneamente.
Se true
for aprovado como o segundo argumento para o construtor de pool, o pool armazena objetos usando java.lang.ref.softReference. Isso permite que os objetos na piscina sejam coletados de lixo quando a pressão da memória na JVM é alta. A piscina clean
remove todas as referências suaves cujo objeto foi coletado de lixo. Isso pode reduzir o tamanho da piscina quando nenhuma capacidade máxima foi definida. Quando a piscina tem uma capacidade máxima, não é necessário chamar clean
, porque a piscina free
tentará remover uma referência vazia se a capacidade máxima for atingida.
O terceiro parâmetro do pool é a capacidade máxima. Se um objeto for libertado e o pool já contiver o número máximo de objetos livres, o objeto especificado será redefinido, mas não adicionado ao pool. A capacidade máxima pode ser omitida sem limite.
Se um reset
implementa pool. Isso dá ao objeto a chance de redefinir seu estado para reutilizar no futuro. Como alternativa, reset
do pool pode ser substituída para redefinir objetos. A entrada e saída implementam o pool para definir sua position
e total
para 0. O Kryo não implementa o pool, porque seu estado de gráfico de objetos é normalmente redefinido automaticamente após cada serialização (consulte Reset). Se você desativar a redefinição automática via setAutoReset(false)
, chame Kryo.reset()
antes de devolver a instância ao pool.
Pool getFree
Returns O número de objetos disponíveis para serem obtidos. Se estiver usando referências suaves, esse número pode incluir objetos que foram coletados no lixo. clean
pode ser usado primeiro para remover referências suaves vazias.
O Pool getPeak
retorna o maior número de objetos gratuitos de todos os tempos. Isso pode ajudar a determinar se a capacidade máxima de um pool é definida adequadamente. Pode ser redefinido a qualquer momento com resetPeak
.
O KRYO fornece vários arquivos de referência baseados em JMH e arquivos R/GGPLOT2.
O KRYO pode ser comparado a muitas outras bibliotecas de serialização no projeto dos serializadores da JVM. Os benchmarks são pequenos, datados e caseiros, em vez de usar JMH, por isso são menos confiáveis. Além disso, é muito difícil comparar minuciosamente as bibliotecas de serialização usando uma referência. As bibliotecas têm muitos recursos diferentes e geralmente têm objetivos diferentes, para que possam se destacar em resolver problemas completamente diferentes. Para entender esses benchmarks, o código executado e os dados que estão sendo serializados devem ser analisados e contrastados com suas necessidades específicas. Alguns serializadores são altamente otimizados e usam páginas de código, outros usam apenas algumas linhas. É bom mostrar o que é possível, mas pode não ser uma comparação relevante para muitas situações.
Existem vários projetos usando o Kryo. Alguns estão listados abaixo. Por favor, envie uma solicitação de tração se quiser que o seu projeto incluísse aqui.