Kryo — это быстрая и эффективная среда сериализации графов двоичных объектов для Java. Цели проекта — высокая скорость, небольшой размер и простой в использовании API. Проект полезен в любое время, когда необходимо сохранить объекты, будь то в файле, базе данных или по сети.
Kryo также может выполнять автоматическое глубокое и поверхностное копирование/клонирование. Это прямое копирование от объекта к объекту, а не от объекта к байтам к объекту.
Эта документация предназначена для Kryo версии 5.x. См. Wiki для версии 4.x.
Пожалуйста, используйте список рассылки Kryo для вопросов, обсуждений и поддержки. Пожалуйста, ограничьте использование системы отслеживания ошибок Kryo ошибками и улучшениями, а не вопросами, обсуждениями или поддержкой.
Kryo публикует два типа артефактов/банок:
JAR-файлы Kryo доступны на странице выпусков и на Maven Central. Последние снимки Kryo, включая сборки снимков master, находятся в репозитории Sonatype.
Чтобы использовать последнюю версию Kryo в своем приложении, используйте эту запись зависимости в вашем pom.xml
:
< dependency >
< groupId >com.esotericsoftware</ groupId >
< artifactId >kryo</ artifactId >
< version >5.6.2</ version >
</ dependency >
Чтобы использовать последнюю версию Kryo в библиотеке, которую вы хотите опубликовать, используйте эту запись зависимости в вашем pom.xml
:
< dependency >
< groupId >com.esotericsoftware.kryo</ groupId >
< artifactId >kryo5</ artifactId >
< version >5.6.2</ version >
</ dependency >
Чтобы использовать последний снимок Kryo, используйте:
< 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 >
Не все являются поклонниками Maven. Использование Kryo без Maven требует размещения JAR Kryo в вашем пути к классам вместе с JAR-файлами зависимостей, найденными в lib.
Для сборки Kryo из исходного кода требуется JDK11+ и Maven. Чтобы построить все артефакты, запустите:
mvn clean && mvn install
Забегая вперед, покажем, как можно использовать библиотеку:
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 ;
}
}
Класс Kryo автоматически выполняет сериализацию. Классы вывода и ввода обрабатывают буферизацию байтов и, при необходимости, их сброс в поток.
Остальная часть этого документа подробно описывает, как это работает, и расширенное использование библиотеки.
Получение данных в Kryo и из него осуществляется с использованием классов ввода и вывода. Эти классы не являются потокобезопасными.
Класс Output — это OutputStream, который записывает данные в буфер массива байтов. Этот буфер можно получить и использовать напрямую, если требуется массив байтов. Если для вывода задан OutputStream, он будет сбрасывать байты в поток, когда буфер заполнится, в противном случае вывод может автоматически увеличить свой буфер. В выводе имеется множество методов для эффективной записи примитивов и строк в байты. Он предоставляет функциональные возможности, аналогичные DataOutputStream, BufferedOutputStream, FilterOutputStream и ByteArrayOutputStream, и все это в одном классе.
Совет: Вывод и ввод обеспечивают все функциональные возможности ByteArrayOutputStream. Редко возникает причина для сброса вывода в ByteArrayOutputStream.
Выходные данные буферизуют байты при записи в Выходной поток, поэтому после завершения записи необходимо вызвать flush
или close
, чтобы буферизованные байты были записаны в Выходной поток. Если для вывода не предоставлен выходной поток, вызов flush
или close
не требуется. В отличие от многих потоков, экземпляр вывода можно использовать повторно, задав позицию или установив новый массив байтов или поток.
Совет: поскольку выходные буферы уже есть, нет смысла сбрасывать выходные данные в BufferedOutputStream.
Конструктор вывода с нулевым аргументом создает неинициализированный вывод. Перед использованием вывода необходимо вызвать setBuffer
.
Класс Input — это InputStream, который считывает данные из буфера массива байтов. Этот буфер можно установить напрямую, если требуется чтение из массива байтов. Если входу присвоен входной поток, он заполнит буфер из потока, когда все данные в буфере будут прочитаны. Ввод имеет множество методов для эффективного чтения примитивов и строк из байтов. Он предоставляет функциональность, аналогичную DataInputStream, BufferedInputStream, FilterInputStream и ByteArrayInputStream, и все это в одном классе.
Совет: Вход предоставляет все функциональные возможности ByteArrayInputStream. Редко возникает причина для чтения ввода из ByteArrayInputStream.
Если вызывается close
ввода, входной поток InputStream закрывается, если таковой имеется. Если вы не читаете из InputStream, нет необходимости вызывать close
. В отличие от многих потоков, экземпляр ввода можно использовать повторно, задав позицию и предел или установив новый массив байтов или входной поток.
Конструктор ввода с нулевым аргументом создает неинициализированный ввод. Прежде чем можно будет использовать вход, необходимо вызвать setBuffer
.
Классы ByteBufferOutput и ByteBufferInput работают точно так же, как Output и Input, за исключением того, что они используют ByteBuffer, а не массив байтов.
Классы UnsafeOutput, UnsafeInput, UnsafeByteBufferOutput и UnsafeByteBufferInput работают точно так же, как их небезопасные аналоги, за исключением того, что во многих случаях они используют sun.misc.Unsafe для повышения производительности. Чтобы использовать эти классы, Util.unsafe
должно быть истинным.
Обратной стороной использования небезопасных буферов является то, что собственный порядок байтов и представление числовых типов системы, выполняющей сериализацию, влияют на сериализованные данные. Например, десериализация завершится неудачно, если данные записываются на X86 и читаются на SPARC. Кроме того, если данные записываются с использованием небезопасного буфера, их необходимо читать с использованием небезопасного буфера.
Самая большая разница в производительности с небезопасными буферами наблюдается при использовании больших примитивных массивов, когда не используется кодирование переменной длины. Кодирование переменной длины можно отключить для небезопасных буферов или только для определенных полей (при использовании FieldSerializer).
Классы IO предоставляют методы для чтения и записи значений переменной длины int (varint) и long (varlong). Это делается с помощью 8-го бита каждого байта, чтобы указать, следуют ли еще байты, что означает, что varint использует 1–5 байтов, а varlong использует 1–9 байтов. Использование кодирования переменной длины обходится дороже, но делает сериализованные данные намного меньше.
При записи значения переменной длины значение можно оптимизировать либо для положительных значений, либо для отрицательных и положительных значений. Например, при оптимизации для положительных значений от 0 до 127 записываются в один байт, от 128 до 16383 в два байта и т. д. Однако небольшие отрицательные числа являются худшим случаем при размере 5 байт. Если эти диапазоны не оптимизированы для положительных значений, они смещаются вдвое вниз. Например, от -64 до 63 записывается одним байтом, от 64 до 8191, от -65 до -8192 - двумя байтами и т. д.
Буферы ввода и вывода предоставляют методы для чтения и записи значений фиксированного или переменной длины. Существуют также методы, позволяющие буферу решать, записывается ли значение фиксированного размера или переменной длины. Это позволяет коду сериализации гарантировать, что кодирование переменной длины используется для очень распространенных значений, которые могли бы раздуть выходные данные, если бы использовался фиксированный размер, в то же время позволяя конфигурации буфера принимать решение для всех других значений.
Метод | Описание |
---|---|
записьИнт (целое) | Записывает 4-байтовое целое число. |
writeVarInt (целое, логическое значение) | Записывает 1-5 байтовое целое число. |
writeInt(целое, логическое значение) | Записывает либо 4, либо 1-5 байтовый int (решает буфер). |
writeLong (длинный) | Записывает длину 8 байт. |
writeVarLong(длинный, логическое значение) | Записывает длину 1-9 байт. |
writeLong(длинный, логическое значение) | Записывает длину либо 8, либо 1-9 байт (решает буфер). |
Чтобы отключить кодирование переменной длины для всех значений, необходимо переопределить методы writeVarInt
, writeVarLong
, readVarInt
и readVarLong
.
Может быть полезно записать длину некоторых данных, а затем данные. Когда длина данных заранее не известна, все данные необходимо буферизовать, чтобы определить их длину, затем можно записать длину, а затем данные. использование для этого одного большого буфера предотвратит потоковую передачу и может потребовать неоправданно большого буфера, что не идеально.
Кодирование фрагментов решает эту проблему за счет использования небольшого буфера. Когда буфер заполнен, записывается его длина, затем данные. Это один фрагмент данных. Буфер очищается, и это продолжается до тех пор, пока не останется данных для записи. Кусок нулевой длины обозначает конец куска.
Kryo предоставляет классы, упрощающие использование фрагментированного кодирования. OutputChunked используется для записи фрагментированных данных. Он расширяет вывод, поэтому имеет все удобные методы для записи данных. Когда буфер OutputChunked заполнен, он сбрасывает чанк в другой выходной поток. Метод endChunk
используется для обозначения конца набора фрагментов.
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 ();
Для чтения фрагментированных данных используется InputChunked. Он расширяет ввод, поэтому имеет все удобные методы чтения данных. При чтении InputChunked будет казаться, что он достигает конца данных, когда он достигает конца набора фрагментов. Метод nextChunks
переходит к следующему набору фрагментов, даже если не все данные были прочитаны из текущего набора фрагментов.
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 ();
Обычно выход и ввод обеспечивают хорошую производительность. Небезопасные буферы работают так же хорошо или даже лучше, особенно для примитивных массивов, если их кроссплатформенная несовместимость приемлема. ByteBufferOutput и ByteBufferInput обеспечивают немного худшую производительность, но это может быть приемлемо, если конечным пунктом назначения байтов должен быть ByteBuffer.
Кодирование переменной длины происходит медленнее, чем фиксированные значения, особенно когда ее использует много данных.
При кодировании фрагментов используется промежуточный буфер, поэтому добавляется одна дополнительная копия всех байтов. Это само по себе может быть приемлемым, однако при использовании в реентерабельном сериализаторе сериализатор должен создавать OutputChunked или InputChunked для каждого объекта. Выделение и сбор мусора этих буферов во время сериализации может отрицательно повлиять на производительность.
Kryo имеет три набора методов для чтения и записи объектов. Если конкретный класс объекта неизвестен и объект может быть нулевым:
kryo . writeClassAndObject ( output , object );
Object object = kryo . readClassAndObject ( input );
if ( object instanceof SomeClass ) {
// ...
}
Если класс известен и объект может быть нулевым:
kryo . writeObjectOrNull ( output , object );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class );
Если класс известен и объект не может быть нулевым:
kryo . writeObject ( output , object );
SomeClass object = kryo . readObject ( input , SomeClass . class );
Все эти методы сначала находят подходящий сериализатор, а затем используют его для сериализации или десериализации объекта. Сериализаторы могут вызывать эти методы для рекурсивной сериализации. Множественные ссылки на один и тот же объект и циклические ссылки обрабатываются Kryo автоматически.
Помимо методов чтения и записи объектов, класс Kryo предоставляет способ регистрации сериализаторов, эффективного чтения и записи идентификаторов классов, обработки нулевых объектов для сериализаторов, которые не могут принимать нулевые значения, а также обработки чтения и записи ссылок на объекты (если они включены). Это позволяет сериализаторам сосредоточиться на своих задачах сериализации.
При тестировании и изучении API Kryo может быть полезно записать объект в байты, а затем прочитать эти байты обратно в объект.
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 );
В этом примере вывод начинается с буфера емкостью 1024 байта. Если на выход будет записано больше байтов, размер буфера увеличится без ограничений. Выходной поток не нужно закрывать, поскольку ему не присвоен выходной поток. Входные данные считываются непосредственно из буфера byte[]
Выходных данных.
Kryo поддерживает создание глубоких и поверхностных копий объектов, используя прямое присвоение одного объекта другому. Это более эффективно, чем сериализация в байты и обратно в объекты.
Kryo kryo = new Kryo ();
SomeClass object = ...
SomeClass copy1 = kryo . copy ( object );
SomeClass copy2 = kryo . copyShallow ( object );
Все используемые сериализаторы должны поддерживать копирование. Все сериализаторы, поставляемые с Kryo, поддерживают копирование.
Как и в случае с сериализацией, при копировании множественные ссылки на один и тот же объект и циклические ссылки обрабатываются Kryo автоматически, если ссылки включены.
Если использовать Kryo только для копирования, регистрацию можно смело отключить.
Kryo getOriginalToCopyMap
можно использовать после копирования графа объектов для получения карты старых объектов в новые. Карта автоматически очищается с помощью Kryo reset
, поэтому полезна только в том случае, если Kryo setAutoReset
имеет значение false.
По умолчанию ссылки не включены. Это означает, что если объект появляется в графе объектов несколько раз, он будет записан несколько раз и будет десериализован как несколько разных объектов. Когда ссылки отключены, циклические ссылки приведут к сбою сериализации. Ссылки включаются или отключаются с помощью Kryo setReferences
для сериализации и setCopyReferences
для копирования.
Когда ссылки включены, varint записывается перед каждым объектом при первом его появлении в графе объектов. При последующих появлениях этого класса в том же графе объектов записывается только варинт. После десериализации ссылки на объекты восстанавливаются, включая любые циклические ссылки. Используемые сериализаторы должны поддерживать ссылки, вызывая reference
Kryo в сериализаторе read
.
Включение ссылок влияет на производительность, поскольку необходимо отслеживать каждый читаемый или записываемый объект.
Под прикрытием ReferenceResolver обрабатывает отслеживание объектов, которые были прочитаны или записаны, и предоставляет целочисленные идентификаторы ссылок. Предусмотрено несколько реализаций:
ReferenceResolver useReferences(Class)
можно переопределить. Он возвращает логическое значение, чтобы решить, поддерживаются ли ссылки для класса. Если класс не поддерживает ссылки, идентификатор ссылки varint не записывается перед объектами этого типа. Если классу не нужны ссылки и объекты этого типа появляются в графе объектов много раз, сериализованный размер можно значительно уменьшить, отключив ссылки для этого класса. Обработчик ссылок по умолчанию возвращает false для всех примитивных оболочек и перечислений. Обычно также возвращается false для String и других классов, в зависимости от сериализуемых графов объектов.
public boolean useReferences ( Class type ) {
return ! Util . isWrapperClass ( type ) && ! Util . isEnum ( type ) && type != String . class ;
}
Распознаватель ссылок определяет максимальное количество ссылок в одном графе объектов. Индексы массивов Java ограничены Integer.MAX_VALUE
, поэтому преобразователи ссылок, использующие структуры данных на основе массивов, могут привести к возникновению исключения java.lang.NegativeArraySizeException
при сериализации более ~ 2 миллиардов объектов. Kryo использует идентификаторы классов int, поэтому максимальное количество ссылок в одном графе объектов ограничено полным диапазоном положительных и отрицательных чисел в int (~ 4 миллиарда).
Kryo getContext
возвращает карту для хранения пользовательских данных. Экземпляр Kryo доступен всем сериализаторам, поэтому эти данные легко доступны всем сериализаторам.
Kryo getGraphContext
аналогичен, но очищается после сериализации или десериализации каждого графа объекта. Это упрощает управление состоянием, которое актуально только для текущего графа объектов. Например, это можно использовать для записи некоторых данных схемы при первом обнаружении класса в графе объектов. Пример см. в разделе CompatibleFieldSerializer.
По умолчанию reset
Kryo вызывается после сериализации каждого всего графа объекта. При этом сбрасываются имена незарегистрированных классов в сопоставителе классов, ссылки на ранее сериализованные или десериализованные объекты в сопоставителе ссылок и очищается контекст графа. Kryo setAutoReset(false)
можно использовать для отключения автоматического вызова reset
, позволяя этому состоянию охватывать несколько графов объектов.
Kryo — это платформа для облегчения сериализации. Сама платформа не навязывает схему и не заботится о том, какие и как данные записываются или читаются. Сериализаторы являются подключаемыми и принимают решения о том, что читать и писать. Многие сериализаторы поставляются «из коробки» для чтения и записи данных различными способами. Хотя предоставленные сериализаторы могут читать и записывать большинство объектов, их можно легко частично или полностью заменить собственными сериализаторами.
Когда Kryo собирается написать экземпляр объекта, сначала ему может потребоваться написать что-то, идентифицирующее класс объекта. По умолчанию все классы, которые Kryo будет читать или писать, должны быть зарегистрированы заранее. Регистрация предоставляет идентификатор класса int, сериализатор, используемый для класса, и создатель экземпляра объекта, используемый для создания экземпляров класса.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Во время десериализации зарегистрированные классы должны иметь те же идентификаторы, что и во время сериализации. При регистрации классу присваивается следующий доступный, наименьший целочисленный идентификатор, что означает, что порядок регистрации классов важен. Идентификатор класса может быть указан явно, чтобы сделать порядок неважным:
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , 9 );
kryo . register ( AnotherClass . class , 10 );
kryo . register ( YetAnotherClass . class , 11 );
Идентификаторы классов -1 и -2 зарезервированы. Идентификаторы классов 0–8 используются по умолчанию для примитивных типов и строк, хотя эти идентификаторы можно переназначить. Идентификаторы записываются как положительные оптимизированные варианты, поэтому наиболее эффективны, когда они представляют собой небольшие положительные целые числа. Отрицательные идентификаторы не сериализуются эффективно.
Под прикрытием ClassResolver фактически обрабатывает чтение и запись байтов для представления класса. Реализации по умолчанию достаточно в большинстве случаев, но ее можно заменить, чтобы настроить, что происходит при регистрации класса, что происходит, когда во время сериализации встречается незарегистрированный класс, а также что читается и записывается для представления класса.
Kryo можно настроить так, чтобы разрешить сериализацию без предварительной регистрации классов.
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Использование зарегистрированных и незарегистрированных классов может быть смешанным. У незарегистрированных классов есть два основных недостатка:
Если использовать Kryo только для копирования, регистрацию можно смело отключить.
Если регистрация не требуется, можно включить Kryo setWarnUnregisteredClasses
для регистрации сообщения при обнаружении незарегистрированного класса. Это можно использовать для легкого получения списка всех незарегистрированных классов. Kryo unregisteredClassMessage
можно переопределить, чтобы настроить сообщение журнала или выполнить другие действия.
При регистрации класса можно дополнительно указать экземпляр сериализатора. Во время десериализации зарегистрированные классы должны иметь те же сериализаторы и конфигурации сериализаторов, что и во время сериализации.
Kryo kryo = new Kryo ();
kryo . register ( SomeClass . class , new SomeSerializer ());
kryo . register ( AnotherClass . class , new AnotherSerializer ());
Если сериализатор не указан или когда встречается незарегистрированный класс, сериализатор выбирается автоматически из списка «сериализаторов по умолчанию», который сопоставляет класс с сериализатором. Наличие большого количества сериализаторов по умолчанию не влияет на производительность сериализации, поэтому по умолчанию Kryo имеет более 50 сериализаторов по умолчанию для различных классов JRE. Могут быть добавлены дополнительные сериализаторы по умолчанию:
Kryo kryo = new Kryo ();
kryo . setRegistrationRequired ( false );
kryo . addDefaultSerializer ( SomeClass . class , SomeSerializer . class );
Output output = ...
SomeClass object = ...
kryo . writeObject ( output , object );
Это приведет к созданию экземпляра SomeSerializer при регистрации SomeClass или любого класса, который расширяет или реализует SomeClass.
Сериализаторы по умолчанию сортируются таким образом, чтобы сначала сопоставлялись более конкретные классы, а в остальном — в порядке их добавления. Порядок их добавления может иметь значение для интерфейсов.
Если ни один сериализатор по умолчанию не соответствует классу, используется глобальный сериализатор по умолчанию. По умолчанию для глобального сериализатора по умолчанию установлено значение FieldSerializer, но его можно изменить. Обычно глобальный сериализатор может обрабатывать множество различных типов.
Kryo kryo = new Kryo ();
kryo . setDefaultSerializer ( TaggedFieldSerializer . class );
kryo . register ( SomeClass . class );
Если в этом коде нет сериализаторов по умолчанию, соответствующих SomeClass, будет использоваться TaggedFieldSerializer.
Класс также может использовать аннотацию DefaultSerializer, которая будет использоваться вместо выбора одного из сериализаторов Kryo по умолчанию:
@ DefaultSerializer ( SomeClassSerializer . class )
public class SomeClass {
// ...
}
Для максимальной гибкости Kryo getDefaultSerializer
можно переопределить, чтобы реализовать собственную логику для выбора и создания экземпляра сериализатора.
Метод addDefaultSerializer(Class, Class)
не позволяет настраивать сериализатор. Вместо класса сериализатора можно установить фабрику сериализатора, что позволит фабрике создавать и настраивать каждый экземпляр сериализатора. Для распространенных сериализаторов предусмотрены фабрики, часто с методом getConfig
для настройки создаваемых сериализаторов.
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 );
Фабрика сериализатора имеет метод isSupported(Class)
, который позволяет ей отказаться от обработки класса, даже если в остальном он соответствует классу. Это позволяет фабрике проверять наличие нескольких интерфейсов или реализовывать другую логику.
Хотя некоторые сериализаторы предназначены для определенного класса, другие могут сериализовать множество разных классов. Сериализаторы могут использовать Kryo newInstance(Class)
для создания экземпляра любого класса. Это делается путем поиска регистрации класса и последующего использования ObjectInstantiator регистрации. Создатель экземпляра может быть указан при регистрации.
Registration registration = kryo . register ( SomeClass . class );
registration . setInstantiator ( new ObjectInstantiator < SomeClass >() {
public SomeClass newInstance () {
return new SomeClass ( "some constructor arguments" , 1234 );
}
});
Если в регистрации нет экземпляра, его предоставляет Kryo newInstantiator
. Чтобы настроить способ создания объектов, можно переопределить Kryo newInstantiator
или предоставить InstantiatorStrategy.
Kryo предоставляет DefaultInstantiatorStrategy, который создает объекты, используя ReflectASM для вызова конструктора с нулевым аргументом. Если это невозможно, он использует отражение для вызова конструктора с нулевым аргументом. Если и это не удается, то либо выдается исключение, либо пробуется резервный вариант InstantiatorStrategy. В Reflection используется setAccessible
, поэтому частный конструктор с нулевым аргументом может быть хорошим способом позволить Kryo создавать экземпляры класса, не затрагивая общедоступный API.
DefaultInstantiatorStrategy — рекомендуемый способ создания объектов с помощью Kryo. Он запускает конструкторы так же, как если бы это было сделано с кодом Java. Альтернативные, экстралингвистические механизмы также могут использоваться для создания объектов. Objenesis StdInstantiatorStrategy использует специальные API-интерфейсы JVM для создания экземпляра класса вообще без вызова какого-либо конструктора. Использование этого опасно, поскольку большинство классов ожидают вызова своих конструкторов. Создание объекта в обход его конструкторов может оставить объект в неинициализированном или недопустимом состоянии. Классы должны быть спроектированы таким образом.
Kryo можно настроить так, чтобы сначала попробовать DefaultInstantiatorStrategy, а затем при необходимости вернуться к StdInstantiatorStrategy.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new StdInstantiatorStrategy ()));
Другой вариант — SerializingInstantiatorStrategy, который для создания экземпляра использует встроенный в Java механизм сериализации. Используя это, класс должен реализовать java.io.Serializable, и вызывается первый конструктор с нулевым аргументом в суперклассе. Это также обходит конструкторы и поэтому опасно по тем же причинам, что и StdInstantiatorStrategy.
kryo . setInstantiatorStrategy ( new DefaultInstantiatorStrategy ( new SerializingInstantiatorStrategy ()));
Альтернативно, некоторые универсальные сериализаторы предоставляют методы, которые можно переопределить для настройки создания объектов для определенного типа вместо вызова 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 );
}
});
Некоторые сериализаторы предоставляют метод writeHeader
, который можно переопределить для записи данных, необходимых для create
в нужное время.
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 ));
}
}
Если сериализатор не предоставляет writeHeader
, запись данных для create
можно выполнить в 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 ());
}
}
Даже если сериализатору известен ожидаемый класс значения (например, класс поля), если конкретный класс значения не является окончательным, тогда сериализатору необходимо сначала записать идентификатор класса, а затем значение. Конечные классы можно сериализовать более эффективно, поскольку они не являются полиморфными.
Kryo isFinal
используется для определения того, является ли класс финальным. Этот метод можно переопределить, чтобы он возвращал true даже для типов, которые не являются окончательными. Например, если приложение широко использует ArrayList, но никогда не использует подкласс ArrayList, обработка ArrayList как финального может позволить FieldSerializer сэкономить 1–2 байта на каждое поле ArrayList.
Kryo может сериализовать замыкания Java 8+, реализующие java.io.Serializable, с некоторыми оговорками. Замыкания, сериализованные на одной JVM, могут не быть десериализованы на другой JVM.
Kryo isClosure
используется для определения того, является ли класс замыканием. Если да, то ClosureSerializer.Closure используется для поиска регистрации класса вместо класса замыкания. Для сериализации замыканий необходимо зарегистрировать следующие классы: ClosureSerializer.Closure, Object[] и Class. Кроме того, необходимо зарегистрировать класс захвата замыкания.
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 );
Сериализация замыканий, которые не реализуют Serializable, возможна с некоторыми усилиями.
Kryo поддерживает потоки, поэтому использовать сжатие или шифрование для всех сериализованных байтов несложно:
OutputStream outputStream = new DeflaterOutputStream ( new FileOutputStream ( "file.bin" ));
Output output = new Output ( outputStream );
Kryo kryo = new Kryo ();
kryo . writeObject ( output , object );
output . close ();
При необходимости сериализатор можно использовать для сжатия или шифрования байтов только подмножества байтов графа объекта. Например, см. DeflateSerializer или BlowfishSerializer. Эти сериализаторы оборачивают другой сериализатор для кодирования и декодирования байтов.
Абстрактный класс Serializer определяет методы для перехода от объектов к байтам и от байтов к объектам.
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 ());
}
}
Сериализатор имеет только два метода, которые необходимо реализовать. write
записывает объект в виде байтов на вывод. read
создает новый экземпляр объекта и считывает данные из входных данных для его заполнения.
Когда Kryo используется для чтения вложенного объекта в read
, сначала необходимо вызвать reference
Kryo с родительским объектом, если вложенный объект может ссылаться на родительский объект. Нет необходимости вызывать reference
Kryo, если вложенные объекты не могут ссылаться на родительский объект, если Kryo не используется для вложенных объектов или если ссылки не используются. Если вложенные объекты могут использовать один и тот же сериализатор, сериализатор должен быть реентерабельным.
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 ;
}
Сериализаторам обычно не следует напрямую использовать другие сериализаторы, вместо этого следует использовать методы чтения и записи Kryo. Это позволяет Kryo организовывать сериализацию и обрабатывать такие функции, как ссылки и нулевые объекты. Иногда сериализатор знает, какой сериализатор использовать для вложенного объекта. В этом случае следует использовать методы чтения и записи Kryo, которые принимают сериализатор.
Если объект может быть нулевым:
Serializer serializer = ...
kryo . writeObjectOrNull ( output , object , serializer );
SomeClass object = kryo . readObjectOrNull ( input , SomeClass . class , serializer );
Если объект не может быть нулевым:
Serializer serializer = ...
kryo . writeObject ( output , object , serializer );
SomeClass object = kryo . readObject ( input , SomeClass . class , serializer );
Во время сериализации Kryo getDepth
предоставляет текущую глубину графа объекта.
При сбое сериализации может быть выброшено исключение KryoException с информацией трассировки сериализации о том, где в графе объекта произошло исключение. При использовании вложенных сериализаторов можно перехватить исключение KryoException для добавления информации трассировки сериализации.
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 ;
}
}
Сериализаторы, предоставляемые Kryo, используют стек вызовов при сериализации вложенных объектов. Kryo минимизирует вызовы стека, но для очень глубоких графов объектов может произойти переполнение стека. Это обычная проблема для большинства библиотек сериализации, включая встроенную сериализацию Java. Размер стека можно увеличить с помощью -Xss
, но учтите, что это относится ко всем потокам. Большие размеры стека в JVM со многими потоками могут использовать большой объем памяти.
Kryo setMaxDepth
можно использовать для ограничения максимальной глубины графа объекта. Это может предотвратить переполнение стека вредоносными данными.
По умолчанию сериализаторы никогда не будут получать нулевое значение, вместо этого Kryo будет записывать байт по мере необходимости, чтобы обозначить ноль или не ноль. Если сериализатор может быть более эффективным, обрабатывая значения NULL, он может вызвать Serializer setAcceptsNull(true)
. Это также можно использовать, чтобы избежать записи нулевого байта, обозначающего значение, когда известно, что все экземпляры, которые будет обрабатывать сериализатор, никогда не будут иметь значение NULL.
Kryo getGenerics
предоставляет информацию об общих типах, поэтому сериализаторы могут быть более эффективными. Чаще всего это используется, чтобы избежать написания класса, когда класс параметра типа является окончательным.
Вывод универсального типа включен по умолчанию и может быть отключен с помощью Kryo setOptimizedGenerics(false)
. Отключение оптимизации обобщений может повысить производительность за счет увеличения сериализованного размера.
Если класс имеет один параметр типа, nextGenericClass
возвращает класс параметра типа или значение null, если его нет. После чтения или записи любых вложенных объектов необходимо вызвать popGenericType
. См. пример CollectionSerializer.
public class SomeClass < T > {
public T value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = kryo . getGenerics (). nextGenericClass ();
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
Для класса с несколькими параметрами типа nextGenericTypes
возвращает массив экземпляров GenericType, а resolve
используется для получения класса для каждого GenericType. После чтения или записи любых вложенных объектов необходимо вызвать popGenericType
. См. пример MapSerializer.
public class SomeClass < K , V > {
public K key ;
public V value ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
kryo . writeObjectOrNull ( output , object . key , serializer );
} else
kryo . writeClassAndObject ( output , object . key );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . getGenerics (). popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class keyClass = null , valueClass = null ;
GenericType [] genericTypes = kryo . getGenerics (). nextGenericTypes ();
if ( genericTypes != null ) {
keyClass = genericTypes [ 0 ]. resolve ( kryo . getGenerics ());
valueClass = genericTypes [ 1 ]. resolve ( kryo . getGenerics ());
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( keyClass != null && kryo . isFinal ( keyClass )) {
Serializer serializer = kryo . getSerializer ( keyClass );
object . key = kryo . readObjectOrNull ( input , keyClass , serializer );
} else
object . key = kryo . readClassAndObject ( input );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
kryo . getGenerics (). popGenericType ();
return object ;
}
}
Для сериализаторов, которые передают информацию о параметрах типа для вложенных объектов в графе объектов (несколько расширенное использование), сначала используется GenericsHierarchy для хранения параметров типа для класса. Во время сериализации Generics pushTypeVariables
вызывается до разрешения универсальных типов (если таковые имеются). Если возвращается >0, за ним должен следовать Generics popTypeVariables
. См. пример FieldSerializer.
public class SomeClass < T > {
T value ;
List < T > list ;
}
public class SomeClassSerializer extends Serializer < SomeClass > {
private final GenericsHierarchy genericsHierarchy ;
public SomeClassSerializer () {
genericsHierarchy = new GenericsHierarchy ( SomeClass . class );
}
public void write ( Kryo kryo , Output output , SomeClass object ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
kryo . writeObjectOrNull ( output , object . value , serializer );
} else
kryo . writeClassAndObject ( output , object . value );
kryo . writeClassAndObject ( output , object . list );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
}
public SomeClass read ( Kryo kryo , Input input , Class <? extends SomeClass > type ) {
Class valueClass = null ;
Generics generics = kryo . getGenerics ();
int pop = 0 ;
GenericType [] genericTypes = generics . nextGenericTypes ();
if ( genericTypes != null ) {
pop = generics . pushTypeVariables ( genericsHierarchy , genericTypes );
valueClass = genericTypes [ 0 ]. resolve ( generics );
}
SomeClass object = new SomeClass ();
kryo . reference ( object );
if ( valueClass != null && kryo . isFinal ( valueClass )) {
Serializer serializer = kryo . getSerializer ( valueClass );
object . value = kryo . readObjectOrNull ( input , valueClass , serializer );
} else
object . value = kryo . readClassAndObject ( input );
object . list = ( List ) kryo . readClassAndObject ( input );
if ( pop > 0 ) generics . popTypeVariables ( pop );
generics . popGenericType ();
return object ;
}
}
Вместо использования сериализатора класс может выполнить собственную сериализацию, реализовав KryoSerializable (аналогично 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 );
}
}
Очевидно, что экземпляр должен быть уже создан до того, как можно будет вызвать read
, поэтому класс не может контролировать свое собственное создание. Класс KryoSerializable будет использовать сериализатор по умолчанию KryoSerializableSerializer, который использует Kryo newInstance
для создания нового экземпляра. Написать собственный сериализатор для настройки процесса, вызова методов до или после сериализации и т. д. тривиально.
Сериализаторы поддерживают копирование только в том случае, если copy
переопределено. Подобно read
из сериализатора, этот метод содержит логику для создания и настройки копии. Как и read
, reference
Kryo должна быть вызвана до того, как Kryo будет использоваться для копирования дочерних объектов, если какой-либо из дочерних объектов может ссылаться на родительский объект.
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 ;
}
}
Вместо использования сериализатора классы могут реализовать KryoCopyable для собственного копирования:
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 ;
}
}
Сериализатор setImmutable(true)
можно использовать, когда тип является неизменяемым. В этом случае copy
сериализатора не требуется — реализация copy
по умолчанию вернет исходный объект.
К нумерации версий Kryo применяются следующие практические правила:
Обновление любой зависимости является значительным событием, но библиотека сериализации более подвержена поломке, чем большинство зависимостей. При обновлении Kryo проверьте различия версий и тщательно протестируйте новую версию в ваших собственных приложениях. Мы стараемся сделать это максимально безопасным и простым.
Сериализаторы Kryo, предоставленные по умолчанию, предполагают, что Java будет использоваться для десериализации, поэтому они не определяют написанный формат. Сериализаторы могут быть записаны с использованием стандартизированного формата, который легче прочиняться другими языками, но это не предоставляется по умолчанию.
Для некоторых потребностей, таких как долгосрочное хранение сериализованных байтов, может быть важно, как сериализация обрабатывает изменения к классам. Это известно как форвардная совместимость (чтение байтов, сериализованных более новыми классами) и обратной совместимости (чтение байтов, сериализованных по старым классам). Kryo предоставляет несколько общих сериалов, которые используют разные подходы к совместимости. Дополнительные сериализаторы могут быть легко разработаны для обратной и обратной совместимости, например, сериализатор, который использует внешнюю, написанную ручную схему.
Когда класс меняется больше, чем его сериализатор может обработать, можно записано сериализатор для передачи данных в другой класс. Все использование старого класса в коде приложения должно быть заменено новым классом. Старый класс хранится исключительно для этого сериализатора.
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 предоставляет много сериалов с различными параметрами конфигурации и уровнями совместимости. Дополнительные сериализаторы можно найти в Сестринском проекте Kryo-Serializers, в котором проводятся сериалы, которые получают доступ к частным API или не совсем безопасны для всех JVM. Больше сериалов можно найти в разделе ссылок.
Fieldserializer работает путем сериализации каждого не переносительного поля. Он может сериализовать Pojos и многие другие классы без какой -либо конфигурации. Все непубличные поля написаны и читаются по умолчанию, поэтому важно оценить каждый класс, который будет сериализован. Если поля являются публичными, сериализация может быть быстрее.
Fieldserializer эффективен, написав только полевые данные, без какой -либо информации схемы, используя файлы класса Java в качестве схемы. Он не поддерживает добавление, удаление или изменение типа полей без аннулирования ранее сериализованных байтов. Поля переименования допускаются только в том случае, если это не изменяет алфавитный порядок полей.
Недостатки совместимости Fieldserializer могут быть приемлемыми во многих ситуациях, например, при отправке данных по сети, но не могут быть хорошим выбором для долгосрочного хранения данных, поскольку классы Java не могут развиваться. Во многих случаях TaggedFieldserializer - лучший выбор.
Параметр | Описание | Значение по умолчанию |
---|---|---|
fieldsCanBeNull | Когда ложно предполагается, что никакие значения поля не являются нулевыми, которые могут сохранить 0-1 байт за поле. | истинный |
setFieldsAsAccessible | Когда это правда, все неверносительные поля (включая частные поля) будут сериализованы и setAccessible если это необходимо. Если неверно, только поля в публичном API будут сериализованы. | истинный |
ignoreSyntheticFields | Если истинно, синтетические поля (генерируемые компилятором для общего пользования) сериализованы. | ЛОЖЬ |
fixedFieldTypes | Если True, предполагается, что конкретный тип значения каждого поля соответствует типу поля. Это устраняет необходимость написать идентификатор класса для значений поля. | ЛОЖЬ |
copyTransient | Если это правда, все переходные поля будут скопированы. | истинный |
serializeTransient | Если это правда, переходные поля будут сериализованы. | ЛОЖЬ |
variableLengthEncoding | Если TRUE, значения переменной длины используются для INT и длинных полей. | истинный |
extendedFieldNames | Если это правда, имена поля префиксируются их объявляющим классом. Это может избежать конфликтов, когда у подкласса есть поле с тем же именем, что и супер класс. | ЛОЖЬ |
Fieldserializer предоставляет поля, которые будут сериализованы. Поля могут быть удалены, поэтому они не будут сериализованы. Поля могут быть настроены, чтобы сделать последовательность более эффективной.
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 ());
Параметр | Описание | Значение по умолчанию |
---|---|---|
canBeNull | Когда ложно предполагается, что значение поля никогда не бывает нулевым, что может сохранить 0-1 байт. | истинный |
valueClass | Устанавливает бетонный класс и сериализатор для использования для значения поля. Это устраняет необходимость написать идентификатор класса для значения. Если класс значения поля является примитивной, примитивной оберткой или окончательным, эта настройка по умолчанию в классе поля. | нулевой |
serializer | Устанавливает сериализатор для использования для значения поля. Если сериализатор установлен, некоторые сериализаторы также требовали, чтобы класс значения также был установлен. Если null, будет использоваться сериализатор, зарегистрированный в Kryo для класса значения поля. | нулевой |
variableLengthEncoding | Если true, используются значения переменной длины. Это относится только к INT или длинным полям. | истинный |
optimizePositive | Если true, положительные значения оптимизированы для значений переменной длины. Это относится к INT или длинным полям только при использовании кодирования переменной длины. | истинный |
Аннотации могут использоваться для настройки сериализаторов для каждого поля.
Аннотация | Описание |
---|---|
@Bind | Устанавливает настройки Cachedfield для любого поля. |
@CollectionBind | Устанавливает настройки CollectionSerializer для полей сбора. |
@MapBind | Устанавливает настройки Mapserializer для поля карты. |
@NotNull | Знамеет поле, как никогда не быть новым. |
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 Extends Fieldserializer и обеспечивает обратную совместимость. Это означает, что поля могут быть добавлены без аннулирования ранее сериализованных байтов. Удаление, переименование или изменение типа поля не поддерживается.
Когда поле добавляется, он должен иметь аннотацию @Since(int)
чтобы указать версию, которую она была добавлена, чтобы быть совместимой с ранее сериализованными байтами. Значение аннотации никогда не должно меняться.
VersionFieldserializer добавляет очень мало накладных расходов к Fieldserializer: один дополнительный валь.
Параметр | Описание | Значение по умолчанию |
---|---|---|
compatible | Когда FALSE, исключение бросается при чтении объекта с другой версией. Версия объекта - максимальная версия любого поля. | истинный |
VersionFieldserializer также наследует все настройки Fieldserializer.
TaggedFieldserializer Extends Fieldserializer для обеспечения обратной совместимости и дополнительной совместимости вперед. Это означает, что поля могут быть добавлены или переименованы и при желании удалены без аннулирования ранее сериализованных байтов. Изменение типа поля не поддерживается.
Только поля, которые имеют аннотацию @Tag(int)
сериализованы. Значения поля тега должны быть уникальными, как в классе, так и во всех его супер -классах. Исключение брошено, если встречаются дубликаты значений тегов.
Переменная и обратная совместимость и производительность сериализации зависят от настройки readUnknownTagData
и chunkedEncoding
. Кроме того, перед каждым поле для значения тега записывается валь.
Когда readUnknownTagData
и chunkedEncoding
ложны, поля не должны быть удалены, но можно применять аннотация @Deprecated
. Умеренные поля читаются при чтении старых байтов, но не записываются на новые байты. Занятия могут развиваться, читая ценности устаревших полей и написав их в другом месте. Поля могут быть переименованы и/или сделаны частными, чтобы уменьшить беспорядок в классе (например, ignored1
, ignored2
).
Taggedfieldserializer (с readUnknownTagData
и chunkedEncoding
false) является предлагаемым сериализатором для большинства классов, где могут быть аннотированы поля. Это позволяет классам развиваться и поля быть удалены из сериализованных данных (посредством снижения), отвечающих потребностям большинства приложений, не добавляя много сериализованного размера.
Параметр | Описание | Значение по умолчанию |
---|---|---|
readUnknownTagData | Когда встречается false и неизвестный тег, бросается исключение или, если chunkedEncoding true, данные пропускаются.Когда TRUE, класс для каждого значения поля записывается до значения. Когда встречается неизвестный тег, сделана попытка прочитать данные. Это используется для пропуска данных, и, если ссылки включены, любые другие значения в графике объекта, ссылаясь на то, что данные все еще могут быть опустошены. Если чтение данных не удалось (например, класс неизвестен или удален), то исключение брошено или, если chunkedEncoding верно, данные пропускаются.В любом случае, если данные пропущены и включены ссылки, то любые ссылки в пропущенных данных не прочитаны, а дальнейшая детериализация может получить неправильные ссылки и провалиться. | ЛОЖЬ |
chunkedEncoding | Когда это правда, поля записываются с помощью кодировки, позволяющей пропустям неизвестные полевые данные. Это влияет на производительность. | ЛОЖЬ |
chunkSize | Максимальный размер каждого куска для кодирования кусочков. | 1024 |
Taggedfieldserializer также наследует все настройки Fieldserializer.
Compatiblefieldserializer Extends Fieldserializer для обеспечения как вперед, так и назад к обратной совместимости. Это означает, что поля могут быть добавлены или удалены без аннулирования ранее сериализованных байтов. Переименование или изменение типа поля не поддерживается. Как и Fieldserializer, он может сериализовать большинство классов без необходимости аннотаций.
Переменная и обратная совместимость и производительность сериализации зависят от настройки readUnknownFieldData
и chunkedEncoding
. Кроме того, в первый раз, когда класс встречается в сериализованных байтах, написана простая схема, содержащая строки имени поля. Поскольку полевые данные идентифицируются по имени, если у супер класса есть поле с тем же именем, что и подкласс, extendedFieldNames
должны быть правдой.
Параметр | Описание | Значение по умолчанию |
---|---|---|
readUnknownFieldData | Когда встречается ложное и неизвестное поле, исключение брошено или, если chunkedEncoding верно, данные пропускаются.Когда TRUE, класс для каждого значения поля записывается до значения. Когда встречается неизвестное поле, сделана попытка прочитать данные. Это используется для пропуска данных, и, если ссылки включены, любые другие значения в графике объекта, ссылаясь на то, что данные все еще могут быть опустошены. Если чтение данных не удалось (например, класс неизвестен или удален), то исключение брошено или, если chunkedEncoding верно, данные пропускаются.В любом случае, если данные пропущены и включены ссылки, то любые ссылки в пропущенных данных не прочитаны, и дальнейшая детериализация может получить неправильные ссылки и провалиться. | истинный |
chunkedEncoding | Когда это правда, поля записываются с помощью кодировки, позволяющей пропустям неизвестные полевые данные. Это влияет на производительность. | ЛОЖЬ |
chunkSize | Максимальный размер каждого куски для кодировки кусочков. | 1024 |
CompatibleFieldSerializer также наследует все настройки FieldSerializer.
Beanserializer очень похож на Fieldserializer, за исключением того, что он использует методы получения бобов и сеттера, а не прямой доступ к полю. Это немного медленнее, но может быть более безопасным, потому что он использует публичный API для настройки объекта. Как и Fieldserializer, он не обеспечивает непредвиденной или обратной совместимости.
CollectionSerializer сериализует объекты, которые реализуют интерфейс java.util.collection.
Параметр | Описание | Значение по умолчанию |
---|---|---|
elementsCanBeNull | Когда ложно предполагается, что никаких элементов в коллекции не являются нулевыми, которые могут сохранить 0-1 байта на элемент. | истинный |
elementClass | Устанавливает бетонный класс для использования для каждого элемента в коллекции. Это устраняет необходимость написать идентификатор класса для каждого элемента. Если класс элементов известен (например, через дженерики) и примитивную, примитивную обертку или финал, то CollectionsEerializer не будет писать идентификатор класса, даже если эта настройка является нулевой. | нулевой |
elementSerializer | Устанавливает сериализатор для использования для каждого элемента в коллекции. Если сериализатор установлен, некоторые сериализаторы также требовали, чтобы класс значения также был установлен. Если будет использоваться сериализатор, зарегистрированный в KRYO для класса каждого элемента. | нулевой |
Mapserializer сериализует объекты, которые реализуют интерфейс java.util.map.
Параметр | Описание | Значение по умолчанию |
---|---|---|
keysCanBeNull | Когда ложно предполагается, что никакие ключи на карте не являются нулевыми, которые могут сохранить 0-1 байт за запись. | истинный |
valuesCanBeNull | Когда ложно предполагается, что никакие значения на карте не являются нулевыми, которые могут сохранить 0-1 байт за запись. | истинный |
keyClass | Устанавливает бетонный класс для использования для каждого ключа на карте. Это устраняет необходимость написать идентификатор класса для каждого ключа. | нулевой |
valueClass | Устанавливает бетонный класс для использования для каждого значения на карте. Это устраняет необходимость написать идентификатор класса для каждого значения. | нулевой |
keySerializer | Устанавливает сериализатор для использования для каждого ключа на карте. Если установлен сериализатор значения, некоторые сериализаторы также требовали, чтобы класс значений также был установлен. Если null, будет использоваться сериализатор, зарегистрированный в Kryo для каждого класса. | нулевой |
valueSerializer | Устанавливает сериализатор для использования для каждого значения на карте. Если установлен ключевой сериализатор, некоторые сериалы потребовали, чтобы класс значений также был установлен. Если null, будет использоваться сериализатор, зарегистрированный в Kryo для каждого класса значения. | нулевой |
Javaserializer и ExmervizatizableSerializer-это сериализаторы Kryo, которые используют встроенную сериализацию Java. Это настолько медленная, как обычная сериализация Java, но может быть необходимо для устаревших классов.
java.io.externalibable и java.io.serializable не имеют сериалистов по умолчанию, установленных по умолчанию, поэтому сериалы по умолчанию должны быть установлены вручную или сериализаторы, установленные при регистрации класса.
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 использует низкую накладную, легкую библиотеку журналов Minlog. Уровень ведения журнала может быть установлен одним из следующих методов:
Log . ERROR ();
Log . WARN ();
Log . INFO ();
Log . DEBUG ();
Log . TRACE ();
Kryo не делает регистрацию в INFO
(по умолчанию) и вышеупомянутых уровнях. DEBUG
удобна для использования во время разработки. TRACE
хорош в использовании при отладке конкретной проблемы, но, как правило, выводит слишком много информации, чтобы оставить.
Minlog поддерживает фиксированный уровень регистрации, который заставляет Java Compiler удалять операторы журнала ниже этого уровня во время компиляции. Крио должен быть составлен с фиксированным уровнем журнала Minlog.
Крио не в безопасности. Каждый поток должен иметь свои экземпляры Kryo, вход и выход.
Поскольку Kryo не является безопасным потоком, а конструирование и настройка экземпляра Kryo является относительно дорогим, в многопоточной среде может быть рассмотрено нитолокальные или объединение.
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 ();
Для объединения Kryo предоставляет класс пула, который может объединить Kryo, вход, вывод или экземпляры любого другого класса.
// 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 );
Если true
передается как первый аргумент в конструктор пула, пул использует синхронизацию внутри и может быть доступен одновременно с несколькими потоками.
Если true
передается как второй аргумент в области конструктора пула, бассейн хранит объекты, используя Java.lang.ref.softreference. Это позволяет объектам в бассейне собираться мусор, когда давление памяти на JVM высокое. Бассейн clean
удаляет все мягкие ссылки, объектом которых был собран мусор. Это может уменьшить размер бассейна, когда максимальная емкость не установлена. Когда бассейн имеет максимальную емкость, нет необходимости вызывать clean
, потому что free
бассейна будет попытаться удалить пустую ссылку, если будет достигнута максимальная емкость.
Третий параметр пула - максимальная емкость. Если объект освобожден, а пул уже содержит максимальное количество свободных объектов, указанный объект сброшен, но не добавляется в пул. Максимальная емкость может быть опущена без ограничения.
Если объект реализует reset
. Это дает объекту возможность сбросить свое состояние для повторного использования в будущем. В качестве альтернативы, reset
пула может быть переопределен для сброса объектов. Входные и выходные реализации Poolable, чтобы установить свою position
, и total
до 0. Kryo не реализует Poolable, поскольку его состояние объекта, как правило, сбрасывается автоматически после каждой сериализации (см. Reset). Если вы отключите автоматический сброс через setAutoReset(false)
, убедитесь, что вы называете Kryo.reset()
перед возвратом экземпляра в бассейн.
Pool getFree
возвращает количество доступных объектов для получения. При использовании мягких ссылок это число может включать объекты, которые были собраны мусором. clean
может быть использована в первую очередь для удаления пустых мягких ссылок.
Bool getPeak
возвращает самое высокое количество бесплатных объектов. Это может помочь определить, установлена ли максимальная емкость пула. Его можно сбросить в любое время с resetPeak
.
Kryo предоставляет ряд критериев на основе JMH и файлов R/GGPLOT2.
Крио можно сравнить со многими другими библиотеками сериализации в проекте JVM Serializers. Цитрицы маленькие, устаревшие и доморощенные, а не используют JMH, поэтому менее заслуживают доверия. Кроме того, очень сложно тщательно сравнить библиотеки сериализации с использованием эталона. Библиотеки имеют много разных функций и часто имеют разные цели, поэтому они могут преуспеть в решении совершенно разных проблем. Чтобы понять эти контрольные показатели, запускается код и сериализованные данные, следует проанализировать и противопоставить ваши конкретные потребности. Некоторые сериализаторы высоко оптимизированы и используют страницы кода, другие используют только несколько строк. Это хорошо, чтобы показать, что возможно, но может не быть соответствующим сравнением для многих ситуаций.
Есть ряд проектов, использующих Kryo. Некоторые из них перечислены ниже. Пожалуйста, отправьте запрос на привлечение, если вы хотите, чтобы ваш проект был включен здесь.