Перейти к основному содержимому

Введение в крио

· 5 мин. чтения

1. Обзор

Kryo — это среда сериализации Java с упором на скорость, эффективность и удобный API.

В этой статье мы рассмотрим ключевые функции платформы Kryo и реализуем примеры, чтобы продемонстрировать ее возможности.

2. Зависимость от Maven

Первое, что нам нужно сделать, это добавить зависимость kryo в наш pom.xml :

<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.1</version>
</dependency>

Последнюю версию этого артефакта можно найти на Maven Central .

3. Основы крио

Давайте начнем с рассмотрения того, как работает Kryo и как с его помощью мы можем сериализовать и десериализовать объекты.

3.1. Введение

Платформа предоставляет класс Kryo в качестве основной точки входа для всех своих функций.

Этот класс организует процесс сериализации и сопоставляет классы с экземплярами Serializer , которые обрабатывают детали преобразования графа объекта в байтовое представление.

Когда байты готовы, они записываются в поток с помощью объекта Output . Таким образом, они могут храниться в файле, базе данных или передаваться по сети.

Позже, когда объект понадобится, экземпляр Input используется для чтения этих байтов и декодирования их в объекты Java.

3.2. Сериализация объектов

Прежде чем углубляться в примеры, давайте сначала создадим служебный метод для инициализации некоторых переменных, которые мы будем использовать для каждого теста в этой статье:

@Before
public void init() {
kryo = new Kryo();
output = new Output(new FileOutputStream("file.dat"));
input = new Input(new FileInputStream("file.dat"));
}

Теперь мы можем посмотреть, как легко писать и читать объект с помощью Kryo:

@Test
public void givenObject_whenSerializing_thenReadCorrectly() {
Object someObject = "Some string";

kryo.writeClassAndObject(output, someObject);
output.close();

Object theObject = kryo.readClassAndObject(input);
input.close();

assertEquals(theObject, "Some string");
}

Обратите внимание на вызов метода close() . Это необходимо, поскольку классы Output и Input наследуются соответственно от OutputStream и InputStream .

Сериализация нескольких объектов также проста:

@Test
public void givenObjects_whenSerializing_thenReadCorrectly() {
String someString = "Multiple Objects";
Date someDate = new Date(915170400000L);

kryo.writeObject(output, someString);
kryo.writeObject(output, someDate);
output.close();

String readString = kryo.readObject(input, String.class);
Date readDate = kryo.readObject(input, Date.class);
input.close();

assertEquals(readString, "Multiple Objects");
assertEquals(readDate.getTime(), 915170400000L);
}

Обратите внимание, что мы передаем соответствующий класс методу readObject() , это делает наш код свободным от приведения.

4. Сериализаторы

В этом разделе мы покажем, какие сериализаторы уже доступны, а затем создадим свои собственные.

4.1. Сериализаторы по умолчанию

Когда Kryo сериализует объект, он создает экземпляр ранее зарегистрированного класса Serializer для преобразования в байты. Они называются сериализаторами по умолчанию и могут использоваться без какой-либо настройки с нашей стороны.

Библиотека уже предоставляет несколько таких сериализаторов, которые обрабатывают примитивы, списки, карты, перечисления и т. д. Если для данного класса сериализатор не найден, то используется FieldSerializer , который может обрабатывать практически любой тип объекта.

Давайте посмотрим, как это выглядит. Во-первых, давайте создадим класс Person :

public class Person {
private String name = "John Doe";
private int age = 18;
private Date birthDate = new Date(933191282821L);

// standard constructors, getters, and setters
}

Теперь давайте напишем объект из этого класса, а затем прочитаем его обратно:

@Test
public void givenPerson_whenSerializing_thenReadCorrectly() {
Person person = new Person();

kryo.writeObject(output, person);
output.close();

Person readPerson = kryo.readObject(input, Person.class);
input.close();

assertEquals(readPerson.getName(), "John Doe");
}

Обратите внимание, что нам не нужно ничего указывать для сериализации объекта Person , поскольку FieldSerializer создается автоматически.

4.2. Пользовательские сериализаторы

Если нам нужно больше контроля над процессом сериализации, у нас есть два варианта; мы можем написать собственный класс Serializer и зарегистрировать его в Kryo или позволить классу самостоятельно выполнять сериализацию.

Чтобы продемонстрировать первый вариант, давайте создадим класс, расширяющий Serializer :

public class PersonSerializer extends Serializer<Person> {

public void write(Kryo kryo, Output output, Person object) {
output.writeString(object.getName());
output.writeLong(object.getBirthDate().getTime());
}

public Person read(Kryo kryo, Input input, Class<Person> type) {
Person person = new Person();
person.setName(input.readString());
long birthDate = input.readLong();
person.setBirthDate(new Date(birthDate));
person.setAge(calculateAge(birthDate));
return person;
}

private int calculateAge(long birthDate) {
// Some custom logic
return 18;
}
}

Теперь давайте проверим:

@Test
public void givenPerson_whenUsingCustomSerializer_thenReadCorrectly() {
Person person = new Person();
person.setAge(0);

kryo.register(Person.class, new PersonSerializer());
kryo.writeObject(output, person);
output.close();

Person readPerson = kryo.readObject(input, Person.class);
input.close();

assertEquals(readPerson.getName(), "John Doe");
assertEquals(readPerson.getAge(), 18);
}

Обратите внимание, что поле age равно 18, хотя ранее мы установили его равным 0.

Мы также можем использовать аннотацию @DefaultSerializer , чтобы сообщить Kryo, что мы хотим использовать PersonSerializer каждый раз, когда ему нужно обрабатывать объект Person . Это помогает избежать вызова метода register() :

@DefaultSerializer(PersonSerializer.class)
public class Person implements KryoSerializable {
// ...
}

Для второго варианта давайте изменим наш класс Person , чтобы расширить интерфейс KryoSerializable :

public class Person implements KryoSerializable {
// ...

public void write(Kryo kryo, Output output) {
output.writeString(name);
// ...
}

public void read(Kryo kryo, Input input) {
name = input.readString();
// ...
}
}

Поскольку тестовый пример для этой опции аналогичен предыдущему, здесь не приводится. Однако вы можете найти его в исходном коде этой статьи.

4.3. Java-сериализатор

В отдельных случаях Kryo не сможет сериализовать класс. Если это произойдет, и написать собственный сериализатор невозможно, мы можем использовать стандартный механизм сериализации Java с помощью JavaSerializer . Это требует, чтобы класс , как обычно, реализовывал интерфейс Serializable .

Вот пример, в котором используется вышеупомянутый сериализатор:

public class ComplexObject implements Serializable {
private String name = "Bael";

// standard getters and setters
}
@Test
public void givenJavaSerializable_whenSerializing_thenReadCorrectly() {
ComplexClass complexObject = new ComplexClass();
kryo.register(ComplexClass.class, new JavaSerializer());

kryo.writeObject(output, complexObject);
output.close();

ComplexClass readComplexObject = kryo.readObject(input, ComplexClass.class);
input.close();

assertEquals(readComplexObject.getName(), "Bael");
}

5. Вывод

В этом уроке мы рассмотрели наиболее примечательные функции библиотеки Kryo.

Мы сериализовали несколько простых объектов и использовали класс FieldSerializer для работы с пользовательским. Мы также создали собственный сериализатор и продемонстрировали, как при необходимости вернуться к стандартному механизму сериализации Java.

Как всегда, полный исходный код этой статьи можно найти на Github .