Для чего нужна сериализация java
Перейти к содержимому

Для чего нужна сериализация java

  • автор:

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

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

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

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

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

Рисунок 1.

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

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

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

* This source code was highlighted with Source Code Highlighter .

В Листинге 1 только одна вещь отличается от создания нормального класса, это реализация интерфейса java.io.Serializable . Интерфейс Serializable это интерфейс-маркер; в нём не задекларировано ни одного метода. Но говорит сериализующему механизму, что класс может быть сериализован.

Теперь у нас есть всё необходимое для сериализации объекта, следующим шагом будет фактическая сериализация объекта. Она делается вызовом метода writeObject() класса java.io.ObjectOutputStream , как показано в листинге 2.

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();
>

* This source code was highlighted with Source Code Highlighter .

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

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

public static void main( String args[]) throws IOException FileInputStream fis = new FileInputStream( «temp.out» );
ObjectInputStream oin = new ObjectInputStream(fis);
TestSerial ts = (TestSerial) oin.readObject();
System. out .println( «version gray»>* This source code was highlighted with Source Code Highlighter .

Восстановление объекта происходит с помощью вызова метода oin.readObject() . В методе происходит чтение набора байт из файла и создаие точной копии графа оригинального объекта. oin.readObject() может прочитать любой сериализованный объект, поэтому необходимо полученный объект приводить к конкретному типу.
Выполненный код выведет version=100 в стандартный вывод.

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

Как должен выглядеть сериализованный объект? Вспомните простой код из предыдущего раздела, который сериализует объект класса TestSerial и записывает в temp.out . В листинге 4 показано содержимое файла temp.out , в шестнадцатеричном виде.

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

Если вы снова посмотрите на TestSerial , то увидите, что у него всего 2 байтовых члена. Как показано в листинге 5.

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

* This source code was highlighted with Source Code Highlighter .

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

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

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

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

В листинге 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();
>
>

* This source code was highlighted with Source Code Highlighter .

В примере сериализуется объект класса SerialTest , который наследуется от parent и содержит объект-контейнер класса contain . В листинге 7 показан сериализованный объект.

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 показан сценарий алгоритма сериализации.

Рисунок 2.

  • AC ED: STREAM_MAGIC . Говорит о том, что используется протокол сериазизации.
  • 00 05: STREAM_VERSION . Версия сериализации.
  • 0x73: TC_OBJECT . Обозначение нового объекта.
  • 0x72: TC_CLASSDESC . Обозначение нового класса.
  • 00 0A : Длина имени класса.
  • 53 65 72 69 61 6c 54 65 73 74: SerialTest , имя класса.
  • 05 52 81 5A AC 66 02 F6: SerialVersionUID , идентификатор класса.
  • 0x02 : Различные флаги. Этот специфический флаг говорит о том, что объект поддерживает сериализацию.
  • 00 02 : Число полей в классе.
  • 0x49 : Код типа поля. 49 это «I», которое закреплено за Int.
  • 00 07 : Длина имени поля.
  • 76 65 72 73 69 6F 6E: version , имя поля.
  • 0x74: TC_STRING . Обозначает новую строку.
  • 00 09 : Длина строки.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain; , Каноническое JVM обозначаение.
  • 0x78: TC_ENDBLOCKDATA , Конец опционального блока данных для объекта.
  • 0x72: TC_CLASSDESC . Обозначение нового класса.
  • 00 06 : Длина имени класса.
  • 70 61 72 65 6E 74: parent , имя класса
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID , идентификатор класса.
  • 0x02 : Различные флаги. Этот флаг обозначает что класс поддерживает сериализацию.
  • 00 01 : Число полей в классе.
  • 0x49 : Код типа поля. 49 обозначает «I», которое закреплено за Int.
  • 00 0D : Длина имени поля.
  • 70 61 72 65 6E 74 56 65 72 73 69 6F 6E : parentVersion, имя поля.
  • 0x78: TC_ENDBLOCKDATA , конец опционального блока данных для объекта.
  • 0x70: TC_NULL , обозначает то что больше нет суперклассов, потому что мы достигли верха иерархии классов.
  • 00 00 00 0A: 10 , Значение parentVersion .
  • 00 00 00 42: 66 , Значение version .

* This source code was highlighted with Source Code Highlighter .

  • 0x73: TC_OBJECT , обозначает новый объект.
  • 0x72: TC_CLASSDESC , обозначает новый класс.
  • 00 07 : Длина имени класса.
  • 63 6F 6E 74 61 69 6E: contain , имя класса.
  • FC BB E6 0E FB CB 60 C7: SerialVersionUID , идентификатор этого класса.
  • 0x02 : Различные флаги. Этот флаг обозначает что класс поддерживает сериализацию.
  • 00 01 : Число полей в классе.
  • 0x49 : Код типа поля. 49 обозначает «I», которое закреплено за Int.
  • 00 0E : Длина имени поля.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion , имя поля.
  • 0x78: TC_ENDBLOCKDATA , конец опционального блока данных для объекта.
  • 0x70: TC_NULL
  • 00 00 00 0B: 11 , значение containVersion .

Заключение

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

Об авторе

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

Зачем нужна Java Serialization? Как сделать класс сериализуемым?

Сериализация – это сохранение типа и состояния объекта в бинарном виде для последующего сохранения/передачи и десериализации. Для сериализации используется интерфейс Serializable ( Externalizable ), и цель записи/источник чтения ObjectInputStream / ObjectOutputStream ( ObjectInput / ObjectOutput ).

Класс сериализуем, если:
�� Реализует маркерный интерфейс Serializable ;
�� Все поля сериализуемые или помечены модификатором transient (иначе рантайм выбросит NotSerializableException ).

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

Для описания полей сериализационной формы в javadoc-документации используется тэг @serial . Для документации генерирующего нестандартную сериализационную форму метода используется @serialData . Эти тэги имеют смысл и для приватных членов, так как эффективно такие члены – часть публичного API.

Нестатические внутренние классы не должны быть сериализуемыми. Статические поля как поля класса а не инстанса несериализуемы.

Сериализации посвящен целый раздел Effective Java. Доклад для ознакомления с темой.

Для чего нужна сериализация java

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

Интерфейс Serializable

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

Сериализация. Класс ObjectOutputStream

Для сериализации объектов в поток используется класс ObjectOutputStream . Он записывает данные в поток.

Для создания объекта ObjectOutputStream в конструктор передается поток, в который производится запись:

ObjectOutputStream(OutputStream out)

Для записи данных ObjectOutputStream использует ряд методов, среди которых можно выделить следующие:

  • void close() : закрывает поток
  • void flush() : очищает буфер и сбрасывает его содержимое в выходной поток
  • void write(byte[] buf) : записывает в поток массив байтов
  • void write(int val) : записывает в поток один младший байт из val
  • void writeBoolean(boolean val) : записывает в поток значение boolean
  • void writeByte(int val) : записывает в поток один младший байт из val
  • void writeChar(int val) : записывает в поток значение типа char, представленное целочисленным значением
  • void writeDouble(double val) : записывает в поток значение типа double
  • void writeFloat(float val) : записывает в поток значение типа float
  • void writeInt(int val) : записывает целочисленное значение int
  • void writeLong(long val) : записывает значение типа long
  • void writeShort(int val) : записывает значение типа short
  • void writeUTF(String str) : записывает в поток строку в кодировке UTF-8
  • void writeObject(Object obj) : записывает в поток отдельный объект

Эти методы охватывают весь спектр данных, которые можно сериализовать.

Например, сохраним в файл один объект класса Person:

import java.io.*; public class Program < public static void main(String[] args) < try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) < Person p = new Person("Sam", 33, 178, true); oos.writeObject(p); >catch(Exception ex) < System.out.println(ex.getMessage()); >> > class Person implements Serializable < private String name; private int age; private double height; private boolean married; Person(String n, int a, double h, boolean m)< name=n; age=a; height=h; married=m; >String getName() int getAge() < return age;>double getHeight() boolean getMarried() >

Десериализация. Класс ObjectInputStream

Класс ObjectInputStream отвечает за обратный процесс — чтение ранее сериализованных данных из потока. В конструкторе он принимает ссылку на поток ввода:

ObjectInputStream(InputStream in)

Функционал ObjectInputStream сосредоточен в методах, предназначенных для чтения различных типов данных. Рассмотрим основные методы этого класса:

  • void close() : закрывает поток
  • int skipBytes(int len) : пропускает при чтении несколько байт, количество которых равно len
  • int available() : возвращает количество байт, доступных для чтения
  • int read() : считывает из потока один байт и возвращает его целочисленное представление
  • boolean readBoolean() : считывает из потока одно значение boolean
  • byte readByte() : считывает из потока один байт
  • char readChar() : считывает из потока один символ char
  • double readDouble() : считывает значение типа double
  • float readFloat() : считывает из потока значение типа float
  • int readInt() : считывает целочисленное значение int
  • long readLong() : считывает значение типа long
  • short readShort() : считывает значение типа short
  • String readUTF() : считывает строку в кодировке UTF-8
  • Object readObject() : считывает из потока объект

Например, извлечем выше сохраненный объект Person из файла:

import java.io.*; public class Program < public static void main(String[] args) < try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) < Person p=(Person)ois.readObject(); System.out.printf("Name: %s \t Age: %d \n", p.getName(), p.getAge()); >catch(Exception ex) < System.out.println(ex.getMessage()); >> >

Теперь совместим сохранение и восстановление из файла на примере списка объектов:

import java.io.*; import java.util.ArrayList; public class Program < //@SuppressWarnings("unchecked") public static void main(String[] args) < String filename = "people.dat"; // создадим список объектов, которые будем записывать ArrayListpeople = new ArrayList(); people.add(new Person("Tom", 30, 175, false)); people.add(new Person("Sam", 33, 178, true)); try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) < oos.writeObject(people); System.out.println("File has been written"); >catch(Exception ex) < System.out.println(ex.getMessage()); >// десериализация в новый список ArrayList newPeople= new ArrayList(); try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) < newPeople=((ArrayList)ois.readObject()); > catch(Exception ex) < System.out.println(ex.getMessage()); >for(Person p : newPeople) System.out.printf("Name: %s \t Age: %d \n", p.getName(), p.getAge()); > > class Person implements Serializable < private String name; private int age; private double height; private boolean married; Person(String n, int a, double h, boolean m)< name=n; age=a; height=h; married=m; >String getName() int getAge() < return age;>double getHeight() boolean getMarried() >

Исключение данных из сериализации

По умолчанию сериализуются все переменные объекта. Однако, возможно, мы хотим, чтобы некоторые поля были исключены из сериализации. Для этого они должны быть объявлены с модификатором transient . Например, исключим из сериализации объекта Person переменные height и married:

class Person implements Serializable < private String name; private int age; private transient double height; private transient boolean married; Person(String n, int a, double h, boolean m)< name=n; age=a; height=h; married=m; >String getName() int getAge() < return age;>double getHeight() boolean getMarried() >

Сериализация простыми словами

Много раз встречал эту «сериализацию» на разных ресурсах, часто связано с JSON. Объясните, пожалуйста, простыми словами, что такое сериализация, где и зачем ее применяют ?

Отслеживать
задан 23 дек 2015 в 22:54
researcher researcher
2,465 1 1 золотой знак 17 17 серебряных знаков 34 34 бронзовых знака

5 ответов 5

Сортировка: Сброс на вариант по умолчанию

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

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

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

Пример: если у вас есть класс

class Test < int length; String name; public Test(int length, String name) < this.length = length; this.name = name; >> 

Объект этого класса в сериализованной форме может иметь вид

Саму сериализацию (и десериализацию) можно производить вручную, или пользоваться соответствующими библиотеками/фреймворками.

Существуют и бинарные форматы сериализации.

Отслеживать
ответ дан 23 дек 2015 в 22:59
206k 28 28 золотых знаков 291 291 серебряный знак 526 526 бронзовых знаков

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

23 дек 2015 в 23:05
@Steve: Не обязательно, вполне возможна и бинарная сериализация.
23 дек 2015 в 23:07
Куда сохраняется сущность при сериализации?
23 дек 2015 в 23:38
@Steve: Куда угодно. В файл, в память, в поток, .
23 дек 2015 в 23:41
Сериализация — преобразование, но не сохранение.
23 дек 2015 в 23:51

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

Если объект сериализуется в текстовую строку, то тут могут быть разные форматы, включая XML и упомянутый в вопросе JSON. Последний для этой цели наиболее популярен в последнее время, т.к. JSON — это и так представление объекта в том формате, как объект создается в JavaScript. Собственно, JSON и переводится как JavaScript Object Notation.

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

Процесс, обратный сериализации, называется десереализация. В случае, если другой процесс получает строку JSON, то он должен десереализовать строку в объект.

Отслеживать
ответ дан 24 дек 2015 в 7:03
1,991 12 12 серебряных знаков 23 23 бронзовых знака
спасибо за пояснение!
24 дек 2015 в 22:05

Если на кошках Вы пишете эмулятор кота — тамагочи. У вас есть объект класс cat

class Cat < private int age; private int weight; // other cat logic >

Вы хотите, чтобы при следующем запуске приложения этот же кот продолжал жить своей жизнью, а не пересоздался заново. Для этого вы реализуете сериализацию\десериализацию кота — то есть созранение\загрузку. Как — так как вам удобно. Можно сделать его Serializable и хранить в бинарном виде, можно сохранять в текстовый файл как JSON (JavaScript Object Notation), можно в базе хранить. Главное — вы сохраняете каким-то образом его состояние (в данном случае — поля), и потом, когда вам это будет нужно, их загружаете. Так же сериализованного кота можно будет например передать по сети на сервер. То есть вы передаете его состояние, а сервер у себя создаст новый объект класса Cat и установит у него это состояние (age и weight).

Отслеживать
ответ дан 24 дек 2015 в 14:57
Ruslan Neruka Ruslan Neruka
226 1 1 серебряный знак 4 4 бронзовых знака
спасибо за простой пример!
24 дек 2015 в 22:06

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

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

Отслеживать
ответ дан 23 дек 2015 в 23:00
339 1 1 серебряный знак 13 13 бронзовых знаков

Сериализация — это преобразование данных. Если из более «понятного» в менее — это сериализация. А наоборот — десериализация.

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

Сериализовать данные нужно, например, для хранения или пересылки. Потому что — есть определённый формат хранения\пересылки данных и данные нужно в него преобразовать.

Очень часто речь идёт о преобразовании в JSON — для отправки на сервер — это сериализация. И обратно — для использования в приложении — это десериализация.

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

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

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

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