что такое стерилизация в java

Сериализация в Java

Зачем сериализация нужна?

В сегодняшнем мире типичное промышленное приложение будет иметь множество компонентов и будет распространено через различные системы и сети. В Java всё представлено в виде объектов; Если двум компонентам Java необходимо общаться друг с другом, то им необходим механизм для обмена данными. Есть несколько способов реализовать этот механизм. Первый способ это разработать собственный протокол и передать объект. Это означает, что получатель должен знать протокол, используемый отправителем для воссоздания объекта, что усложняет разработку сторонних компонентов. Следовательно, должен быть универсальный и эффективный протокол передачи объектов между компонентами. Сериализация создана для этого, и компоненты Java используют этот протокол для передачи объектов.

Рисунок 1 демонстрирует высоко-уровневое представление клиент-серверной коммуникации, где объект передаётся с клиента на сервер посредством сериализации.
что такое стерилизация в java. Смотреть фото что такое стерилизация в java. Смотреть картинку что такое стерилизация в java. Картинка про что такое стерилизация в java. Фото что такое стерилизация в java
Рисунок 1.

Как сериализовать объект?

Для начала следует убедиться, что класс сериализуемого объекта реализует интерфейс java.io.Serializable как показано в листинге 1.

class TestSerial implements Serializable <
public byte version = 100;
public byte count = 0;
>

public static void main( String args[]) throws IOException <
FileOutputStream fos = new FileOutputStream( «temp.out» );
ObjectOutputStream oos = new ObjectOutputStream(fos);
TestSerial ts = new TestSerial();
oos.writeObject(ts);
oos.flush();
oos.close();
>

В листинге 2 показано сохранение состояния экземпляра TestSerial в файл с именем temp.out

Для воссоздания объекта из файла, необходимо применить код из листинга 3.

Формат сериализованного объекта

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 A0 0C 34 00 FE B1 DD F9 02 00 02 42 00 05
63 6F 75 6E 74 42 00 07 76 65 72 73 69 6F 6E 78
70 00 64

public byte version = 100;
public byte count = 0;

Размер байтовой переменной один байт, и следовательно полный размер объекта (без заголовка) — два байта. Но размер сериализованного объекта 51 байт. Удивлены? Откуда взялись эти дополнительные байты и что они обозначают? Они добавлены сериализующим алгоритмом и необходимы для воссоздания объекта. В следующем абзаце будет подробно описан этот алгоритм.

Алгоритм сериализации Java

К этому моменту у вас уже должно быть достаточно знаний, чтобы сериализовать объект. Но как работает этот механизм? Алгоритм сериализации делает следующие вещи:

В листинге 6 указан пример охватывающий все возможные случаи сериализации

class parent implements Serializable <
int parentVersion = 10;
>

class contain implements Serializable <
int containVersion = 11;
>
public class SerialTest extends parent implements Serializable <
int version = 66;
contain con = new contain();

public int getVersion() <
return version;
>
public static void main( String args[]) throws IOException <
FileOutputStream fos = new FileOutputStream( «temp.out» );
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerialTest st = new SerialTest();
oos.writeObject(st);
oos.flush();
oos.close();
>
>

AC ED 00 05 73 72 00 0A 53 65 72 69 61 6C 54 65
73 74 05 52 81 5A AC 66 02 F6 02 00 02 49 00 07
76 65 72 73 69 6F 6E 4C 00 03 63 6F 6E 74 00 09
4C 63 6F 6E 74 61 69 6E 3B 78 72 00 06 70 61 72
65 6E 74 0E DB D2 BD 85 EE 63 7A 02 00 01 49 00
0D 70 61 72 65 6E 74 56 65 72 73 69 6F 6E 78 70
00 00 00 0A 00 00 00 42 73 72 00 07 63 6F 6E 74
61 69 6E FC BB E6 0E FB CB 60 C7 02 00 01 49 00
0E 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E 78
70 00 00 00 0B

На рисунке 2 показан сценарий алгоритма сериализации.
что такое стерилизация в java. Смотреть фото что такое стерилизация в java. Смотреть картинку что такое стерилизация в java. Картинка про что такое стерилизация в java. Фото что такое стерилизация в java
Рисунок 2.

Заключение

В этой статье вы увидели как сериализовать объект, и узнали как работает алгоритм сериализации. Я надеюсь эта статья помогла вам лучше понять что происходит, когда вы сериализуете объект.

Об авторе

Sathiskumar Palaniappan имеет более чем 4-х летний опыт работы в IT-индестрии, и работает с Java технологиями более 3 лет. На данный момент он работает system software engineer в Java Technology Center, IBM Labs. Также имеет опыт работы в телекоммуникационной индустрии.

Источник

Сериализация в Java. Не все так просто

что такое стерилизация в java. Смотреть фото что такое стерилизация в java. Смотреть картинку что такое стерилизация в java. Картинка про что такое стерилизация в java. Фото что такое стерилизация в java

Сериализация (Serialization) — это процесс, который переводит объект в последовательность байтов, по которой затем его можно полностью восстановить. Зачем это нужно? Дело в том, при обычном выполнении программы максимальный срок жизни любого объекта известен — от запуска программы до ее окончания. Сериализация позволяет расширить эти рамки и «дать жизнь» объекту так же между запусками программы.

Дополнительным бонусом ко всему является сохранение кроссплатформенности. Не важно какая у вас операционная система, сериализация переводит объект в поток байтов, который может быть восстановлен на любой ОС. Если вам необходимо передать объект по сети, вы можете сериализовать объект, сохранить его в файл и передать по сети получателю. Он сможет восстановить полученный объект. Так же сериализация позволяет осуществлять удаленный вызов методов (Java RMI), которые находятся на разных машинах с, возможно, разными операционными системами, и работать с ними так, словно они находятся на машине вызывающего java-процесса.

Реализовать механизм сериализации довольно просто. Необходимо, чтобы ваш класс реализовывал интерфейс Serializable. Это интерфейс — идентификатор, который не имеет методов, но он указывает jvm, что объекты этого класса могут быть сериализованы. Так как механизм сериализации связан с базовой системой ввода/вывода и переводит объект в поток байтов, для его выполнения необходимо создать выходной поток OutputStream, упаковать его в ObjectOutputStream и вызвать метод writeObject(). Для восстановления объекта нужно упаковать InputStream в ObjectInputStream и вызвать метод readObject().

В процессе сериализации вместе с сериализуемым объектом сохраняется его граф объектов. Т.е. все связанные с этим объекто, объекты других классов так же будут сериализованы вместе с ним.

Рассмотри пример сериализации объекта класса Person.

Вывод:

В данном примере класс Home создан для того чтобы продемонстрировать, что при сериализации объекта Person, с ним сериализуется и граф его объектов. Класс Home так же должен реализовывать интерфейс Serializable, иначе случится исключение java.io.NotSerializableException. Так же в примере описана сериализация с помощью класса ByteArrayOutputStream.

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

Однако, так же видно, что при выполнении записи двумя потоками вывода (у нас это ObjectInputStream и ByteArrayOutputStream), объект home будет создан заново, несмотря на то, что он уже был создан до этого в одном из потоков. Мы видим это по разным адресам объектов home, полученных в двух потоках. Получается, что если выполнить сериализацию одним выходным поток, затем восстановить объект, то у нас есть гарантия восстановления полной сети объектов без лишних дубликатов. Конечно, в ходе выполнения программы состояние объектов может измениться, но это на совести программиста.

Проблема

Из примера так же видно, что при восстановлении объекта может возникнуть исключение ClassNotFoundException. С чем это связано? Дело в том, что мы легко можем сериализовать объект класса Person в файл, передать его по сети нашему товарищу, который может восстановить объект другим приложением, в котором класса Person попросту нет.

Своя сериализация. Как сделать?

Что делать, если вы хотите управлять сериализацией сами? Например, ваш объект хранит в себе логин и пароль пользователей. Вам необходимо сериализовать его для дальнейшей передачи его по сети. Передавать пароль в таком случае крайне ненадежно. Как решить эту задачу? Существует два способа. Первый, использовать ключевое слово transient. Второй, вместо реализации интереса Serializable использовать его расширение — интерфейс Externalizable. Рассмотрим примеры работы первого и второго способа для их сравнения.

Первый способ — Сериализация с использованием transient

Вывод:

Второй способ — Сериализация с реализацией интерфейса Externalizable

Вывод:

Первое отличие двух вариантов, которое бросается в глаза это размер кода. При реализации интерфейса Externalizable нам необходимо переопределить два метода: writeExternal() и readExternal(). В методе writeExternal() мы указываем какие поля будут сериализованы и как, в readExternal() как их прочитать. При использовании слова transient мы явно указываем, какое поле или поля не нужно сериализовывать. Так же заметим, что во втором способе мы явно создали конструктор по умолчанию, причем публичный. Зачем это сделано? Давайте попробуем запустить код без этого конструктора. И посмотрим на вывод:

Мы получили исключение java.io.InvalidClassException. С чем это связано? Если пройти по стек-трейсу можно выяснить, что в конструкторе класса ObjectStreamClass есть строчки:

Для интерфейса Externalizable будет вызван метод получения конструктора getExternalizableConstructor(), внутри которого мы через Reflection попробуем получить конструктор по умолчанию класса, для которого мы восстанавливаем объект. Если нам не удается его найти, или он не public, то мы получаем исключение. Обойти эту ситуацию можно следующим образом: не создавать явно никакого конструктора в классе и заполнять поля с помощью сеттеров и получать значение геттерами. Тогда при компиляции класса будет создан конструктор по умолчанию, который будет доступен для getExternalizableConstructor(). Для Serializable метод getSerializableConstructor() получает конструктор класса Object и от него ищет нужный класс, если не найдет, то получим исключение ClassNotFoundException. Выходит, что ключевое различие между Serializable и Externalizable в том, что первому не нужен конструктор для создания восстановления объекта. Он просто полностью восстановится из байтов. Для второго при восстановлении сначала будет создан объект с помощью конструктора в точке объявления, а затем в него будут записаны значения его полей из байтов, полученных при сериализации. Лично мне больше нравится первый способ, он гораздо проще. Причем, даже если нам нужно все таки задать поведение сериализации, мы можем не использовать Externalizable, а так же реализовать Serializable, добавив (не переопределив) в него методы writeObject() и readObject(). Но для того, чтобы они «работали» нужно точно соблюсти их сигнатуру.

Вывод:

Внутри наших добавленных методов вызываются defaultWriteObject() и defaultReadObject(). Они отвечают за сериализацию по умолчанию, как если бы она работала без добавленных нами методов.

На самом деле это только верхушка айсберга, если продолжить углубляться в механизм сериализации, то с высокой доли вероятности, можно отыскать еще нюансы, найдя которые мы скажем: «Сериализация… не все так просто».

Источник

Сериализация данных: тест производительности и описание применения

Операции сохранения и восстановления данных применяются очень часто. В классических языках программирования готовых механизмов для сохранения и восстановления данных объектов нет и, при возникновении такой необходимости, приходится создавать их самостоятельно.

Сериализация

В классических языках программирования нет готовых возможностей для сохранения структурированных объектов, но, т.к. структуры данных напрямую лежат в памяти, есть легкий способ сохранения и восстановления данных напрямую. С одной стороны, создавать собственные средства для сохранения нужно только в том случае, когда требуется сохранять сложные объекты со взаимосвязями друг с другом но, с другой, даже если хочется использовать какой-то максимально простой готовый механизм, то реализация сохранения данных в его формат – это довольно трудоемкая операция.

Многие классы потоков, такие как Writer или PrintStream предоставляют готовые возможности для сохранения элементарных типов данных, но использовать эти так же неудобно, как и в классических языках программирования из-за очень большого числа описаний, которые необходимо проделывать.
Но, помимо работы с элементарными типами, в Java существует несколько разных типов готовых механизмов для сохранения данных классов и множество библиотек, реализующих работу с одними и теми же форматами, отличающихся друг от друга производительностью, объемом и предоставляемыми возможностями.

Serializable

Простейшая возможность, существующая в стандартной библиотеке Java – это сохранение и восстановление данных в полностью автоматическом режиме в бинарной форме. Для реализации этой возможности необходимо всего лишь указать у всех классов, данные которых должны автоматически сохраняться и восстанавливаться, интерфейс Serializable в качестве реализуемого. Это интерфейс «маркер», который не требует реализации ни одного метода. Он используется просто для обозначения того, что данные этого класса должны сохраняться и восстанавливаться.

Пример использования

Использование этого класса элементарно – одной операцией и не требует написания ни одной лишней буквы.

В результате выполнения этого теста мы получим следующий вывод:

Как уже видно из результата сохранение и восстановление объекта прошло успешно и, после восстановления, новый объект имеет точно такое же содержимое как сохраняемый.
При выполнении программы был создан файл » out.bin » размером в 244 байта в бинарном формате. Описание формата можно найти во множестве источников, но, на мой взгляд, разбираться в нем не имеет никакого смысла, достаточно, чтобы его успешно понимали операции сохранения и восстановления.

Особенности

Если рассмотреть приведенный выше пример подробнее, то можно увидеть следующие особенности.

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

Для сохранения данных используется специальный поток ObjectOutputStream (и аналоги) для загрузки. Этот поток умеет работать с любыми типами данных и, в том числе, с объектами целиком, чем мы и воспользовались. Формируемые этим потоком данные содержат независимый набор блоков информации, поэтому никаких ограничений на его использование нет. Можно сохранять в один поток сколько угодно объектов или элементарных типов, главное, при восстановлении, прочитать их в обратном порядке.

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

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

Возможности

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

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

Поддерживается сохранение классов типа «enum» с корректным их восстановлением.

Поддерживается сохранение и восстановление любых объектов, которые имеет интерфейс-маркер Serializable. В частности, будут автоматически сохраняться все стандартные JDK коллекции, основанные на List, Set и Map т.к. все их реализации этот маркер имеют.
Т.е. для того чтобы сохранить и восстановить все элементы списка или даже дерева не нужно писать никакого дополнительного кода, достаточно чтобы объекты были обозначены интерфейсом «Serializable».

Для более точного управления процессом сохранения и восстановления данных можно использовать дополнительные механизмы.

Контроль версии

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

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

Код описывающий состояние класса может быть вычислен автоматически, при сохранении объекта, но, если класс не планируется изменять в дальнейшем или хочется отключить этот механизм проверки, то можно использовать специальное поле класса.

При восстановлении данных проверяется значение кода из потока с тем, которое вычислено или записано константой у нужного класса в момент сохранения и, если эти значения не совпадают, будет выброшено исключение с ошибкой.
Тип доступа поля serialVersionUID не играет никакой роли, оно может быть как публичным, так и скрытым.
После того, как класс принял окончательную форму и его изменение более не планируется, рекомендуется описать эту константу в классе, чтобы избежать ее расчета при каждой загрузке и сохранении. Значение этой константы может отражать реальное состояние класса, и тогда ее нужно вычислить с помощью методов библиотеки, или содержать любое произвольное значение, если соответствие класса не важно.

Управление сохраняемыми данными

Часто сохранять и восстанавливать нужно не все существующие у объекта данные, но только часть из них или восстанавливать их в формате, которые не соответствует их фактическому типу.

Первой возможностью управления является механизм исключений.
Для того, чтобы исключить какое-то поле из списка обрабатываемых его нужно пометить специальным типом «transient». В Java для этого используется специальное ключевое слово, а в Kotlin необходимо использовать специальную аннотацию.

При обработке объектов этого класса библиотека сериализации не будет ни сохранять ни восстанавливать значения для поля » dbField «. Все остальные поля будут сохранены и восстановлены как обычно.
Этот механизм удобно использовать в случаях, когда объекты поля, значения которых не имеет смысла или нельзя сохранять.
Устанавливать значения полей, которые не будут обрабатываться автоматически, программист должен самостоятельно, после загрузки. Для этого можно воспользоваться методом » readResolve «, который описан ниже.

Теперь наш пример сохраняет только два поля из трех доступных.

Сохранение и восстановление данных вручную

Иногда, описать структуру или изменения в ней так, чтобы автоматизированные средства работали без ошибок не удается. В таком случае нужно сохранять или восстанавливать значения полей класса вручную, но при этом не хотелось бы лишаться всех преимуществ, предоставляемых автоматизированными средствами.

В тексте функций writeObject и readObject можно реализовать произвольную логику сохранения и загрузки данных.
Можно пользоваться механизмами, предоставляемыми библиотекой, как это реализовано в примере выше, а можно реализовать сохранение и восстановление объекта полностью вручную. Правда в последнем случае будет сложно обеспечить преемственность сохраняемой структуры, как это реализовано в примере, но, часто, такой необходимость нет. В случае ручного оперирования данными нужно обеспечить, чтобы данные восстанавливались в том же порядке, в котором они были сохранены.

Восстановление синглетонов

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

Это поведение, так же, можно обеспечить.

Результат работы программы:

Теперь, при загрузке любого объекта класса LinkedData у загруженного объекта будет вызвана функция readResolve и, если понадобится заменить загруженный объект другим, достаточно вернуть его из этого метода.
В нашем примере код вернет один из уже существующих объектов этого класса, обеспечив его уникальность и идентичность.

Сохранение нестандартных классов, прокси

Первый предназначен для обеспечения загрузки и сохранения неизвестных классов, а второй для проксирования интерфейса классов.

Недостатки

Библиотека, реализующая функционал интерфейса Serializable очень могучая, но она имеет ряд серьезных недостатков, которые в некоторых случаях могут являться принципиальным ограничением для ее использования.

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

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

Формат сохраняемых данных – бинарный.

Если когда-то может возникнуть задача, получить данные из такого файла на другом языке программирования или обеспечить возможность просмотра или редактирования его человеком, то такая задача может оказаться довольно сложно реализуемой.

ВАЖНО: Serializable работает только с полями!

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

Если вы реализуете интерфейс, где значение поля «эмулируется» парой функций для установки и получения значения, если в ваших класса используются делегаты и прочие способы описания свойств, которые не имеют соответствующего поля в классе, то этот механизм окажется абсолютно бесполезен. Он способен сохранить и восстановить только данные, описанные в классе в виде полей, а для всех остальных придется реализовывать их сохранение и загрузку вручную.

Externalizable

Принципиальное отличие работы кода Externalizable заключается в следующем:

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

Сохранено будет только то, что явно закодировано в функции сохранения и именно в той форме, в которой оно закодировано. Никакой автоматизации по сохранению полей объекта не будет.

Любая автоматизация по восстановлению объектов и ссылок на них будет производиться только тогда, когда они сохраняются и восстанавливаются как объекты.

Никакой дополнительной информации, кроме сохраняемой каждым элементарным методом, в поток записано не будет.

В этом коде пришлось изменить описание поля link на изменяемое т.к. его приходится устанавливать вручную при загрузке и описать конструктор для создания загружаемого объекта. С целью иллюстрации преимуществ такого способа сохранения данных вместо объекта link сохраняется только описание его состояния.

Код загрузки и восстановления остался тем же, что использовался с примерами ранее, в результате в поток было сохранено имя объекта, но другой служебной информации в нем нет. В результате, объем сохраненного файла уменьшился в несколько раз.
Можно переписать код сохранения объекта «DataClass» с использованием явного вызова методов сохранения и восстановления, тогда доля служебной информации в полученном файле станет совсем незначительной.

Особенности

То, в какую сторону будет смещаться удобство или эффективность кода зависит от тех методов, которые будут использованы в в программе. Чем больше используется автоматизации, тем больше будет сохранено служебной информации, и тем медленнее будет работать код, но тем меньшее число нюансов сохранения и восстановления данных придется реализовывать вручную.

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

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

К примеру, если сохранять абсолютно все данные вручную, то использование классов ObjectStream теряет всякий смысл, а связанные с их использованием накладные расходы и ограничения могут только мешать. Становится гораздо проще использовать возможности записи и чтения данных, предоставляемые любым бинарным потоком.

Тестирование, сравнения

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

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

Автоматически генерируемые случайные данные выглядят следующим образом:

Имена методов, типов и исходных файлов генерируются из набора случайных имен.
Имя исходного файла может отсутствовать, а текст и void обозначает синглетоны, обозначающие уникальные значения для имени файла или параметра.

Данные, которые хранятся в классах только строкового типа, т.е. ни работу с массивами данных, ни с примитивными типами этот тест не использует. Тем не менее, я считаю, что полученные на нем результаты вполне отражают соотношение производительности различных методов друг с другом, хотя доля этого соотношения будет меняться в зависимости от типов данных и их количества.

Используемые средства

При тестировании использовались следующие способы сохранения и восстановления данных:

Extern+Ser — Реализация интерфейса Externalizable с кодом, в котором смешано ручное и автоматизированное сохранение данных.

ExternFull — Реализация интерфейса Externalizable с полностью ручным сохранением данных.

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

НазваниеВерсияИсточникДополнительные библиотеки
XML, Serializable, ExternalizableJava 1.8Штатная реализация JavaНе требуются, входят в комплект поставки Java.
minimal-json0.9.4https://github.com/ralfstx/minimal-jsonminimal-json-0.9.4.jar – 30Кб
fasterXML-jackson2.0.4https://github.com/FasterXML/jacksonjackson-core-2.0.4.jar – 194Кб, jackson-databind-2.0.4.jar – 847Кб, jackson-annotations-2.0.4.jar – 34Кб

Процедура тестирования

Для тестирования была реализована утилита, исходный тест которой доступен по ссылке, приведенной в конце.

Эта утилита последовательно производит следующие действия:

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

Результаты

В зависимости от количества данных и выделяемой JVM памяти результаты значительно отличаются в абсолютных цифрах, однако, их соотношение сохраняется практически в любых условиях.

МестоИмяЗаписьЛучшХудшЗагрузкаЛучшХудшВсегоЛучшХудшФайл
6SerialFull0:00:07.5992,340:00:04.2171,051,450:00:11.8261,560,4118Мб
1ExternFull0:00:02.5500,121,980:00:02.0614,020:00:04.6162,6016Мб
5Extern+Ser0:00:05.7441,520,320:00:04.1121,001,510:00:09.8621,140,6922.5Мб
7XMLw3c0:00:06.2781,760,210:00:10.3374,020:00:16.6202,6032Мб
4JsJsonMini0:00:04.6781,050,620:00:04.6141,241,240:00:09.3021,020,7925.9Мб
3JsJackAnn0:00:02.7760,221,740:00:02.4310,183,250:00:05.2150,132,1925.9Мб
2JsJackSream0:00:02.2782,340:00:02.3770,153,350:00:04.6620,012,5625.9Мб

В этой таблице приведены результаты тестирования.

Комментарии

Serializable

Этот механизм очень медленный.

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

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

Externalizable

Этот способ предсказуемо оказался самым скоростным при работе и генерирующим минимальные файлы данных.

Однако скоростные и объемные показатели этого теста становятся заметными только при полном отказе от автоматизации для всех, часто используемых операций. Как только автоматизация используется более широко (тест Extern+Ser ) производительность программы стремительно падает, а объем файла данных растет. Причины этого явления описаны в главе ранее.

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

Дополнительным минусом, который стоит отметить, является трудоемкость поиска ошибок рассогласования данных.

minimal-json

Эта библиотека является очень маленькой по объему, но работает медленно.

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

Маленький размер – это, по сути, единственное достоинство этой библиотеки.

fasterXML-jackson databind

Библиотека очень большая, работает быстро, но требует больших усилий по адаптации данных для ее использования.

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

Реализация этого теста потребовала написания самого большого объема кода, который не относится к выполняемой задаче напрямую. Некоторые вещи в ней даже невозможно реализовать без ручного управления. К примеру, загрузку коллекции, в элементах которой могут быть синглетоны так, чтобы после загрузки они ссылались на уже существующие объекты.

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

Использовать эту библиотеку имеет смысл в случаях, если:

Существует множество несложных классов и часть сохраняемых свойств не существует в виде полей.

На мой взгляд, если в проекте вообще можно использовать библиотеки размером около мегабайта, то этот способ является наиболее предпочтительным т.к. он позволяет работать как с полями так и с методами и при этом не требует большого количества описаний в тексте программы.

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

fasterXML-jackson stream

Самый быстрый способ сохранить и загрузить данные в формате JSON с минимальными требованиями к объему памяти.

Реализация особых сложностей не вызывает.
Особенностью этого способа является то, что при сохранении данных дополнительное дерево для хранения объектов JSON не создается, а сразу формируются данные формата. Аналогично при загрузке, происходит одновременное чтение JSON и его разбор. Особенность непосредственного разбора синтаксиса накладывает заметный отпечаток на алгоритм, которые необходимо реализовывать для загрузки объектов и их свойств.
Возможно, при работе со сложной иерархией данных, такой способ может оказаться крайне неудобен в реализации.

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

Этот способ является самым медленным и предъявляет самые большие требования к объему памяти.

Дерево для всех объектов файла дынных не просто строится при загрузке данных, но при этом потребляет просто неприличный ее объем. При тестировании этот тест не смог выполниться с количеством элементов более 400.000 штук из-за того, что 5Гб выделенной для JVM памяти оказалось недостаточно.

При реализации этот способ абсолютно ничем не отличается от любого другого, где нужно сформировать дерево перед сохранением и разобрать его при загрузке. Небольшое отличие при реализации было только в сравнении с библиотекой fasterXML-jackson в режиме stream т.к. разбирать загруженное дерево всегда проще, чем зависеть от того порядка, в каком элементы окажутся в исходных данных.

Использовать этот способ ни для больших исходных данных, ни для операций, где важна производительность, нельзя. Он работает медленно и потребляет много памяти.

Ссылки

Текст утилиты тестирования можно скачать по ЭТОЙ ССЫЛКЕ.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *