Пользователь
0,0
рейтинг
24 мая 2009 в 04:59

Разработка → Сериализация в Java перевод

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

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



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

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

Рисунок 1.

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



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

Листинг 1.

import java.io.Serializable;

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.

Листинг 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.

Листинг 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="+ts.version);
}


* This source code was highlighted with Source Code Highlighter.

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

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


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

Листинг 4.
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.

Листинг 5.
  public byte version = 100;
  public byte count = 0;


* This source code was highlighted with Source Code Highlighter.

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

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


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

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


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

Листинг 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 показан сериализованный объект.

Листинг 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. Обозначение нового объекта.

На первом шаге алгоритм сериализации записывает описание класса ассоциированного с объектом. В примере был сериализован объект класса SerialTest, следовательно алгоритм начал записывать описание класса SerailTest.
  • 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: Число полей в классе.

Теперь алгоритм записывает поле int version = 66;.
  • 0x49: Код типа поля. 49 это «I», которое закреплено за Int.
  • 00 07: Длина имени поля.
  • 76 65 72 73 69 6F 6E: version, имя поля.

Затем алгоритм записывает следующее поле, contain con = new contain();. Это объект следовательно будет записано каноническое JVM обозначение этого поля.
  • 0x74: TC_STRING. Обозначает новую строку.
  • 00 09: Длина строки.
  • 4C 63 6F 6E 74 61 69 6E 3B: Lcontain;, Каноническое JVM обозначаение.
  • 0x78: TC_ENDBLOCKDATA, Конец опционального блока данных для объекта.

Следующим шагом алгоритма является запись описания класса parent, который является непосредственным суперклассом для SerialTest.
  • 0x72: TC_CLASSDESC. Обозначение нового класса.
  • 00 06: Длина имени класса.
  • 70 61 72 65 6E 74: parent, имя класса
  • 0E DB D2 BD 85 EE 63 7A: SerialVersionUID, идентификатор класса.
  • 0x02: Различные флаги. Этот флаг обозначает что класс поддерживает сериализацию.
  • 00 01: Число полей в классе.

Теперь алгоритм записывает описание полей класса parent, класс имеет одно поле, int parentVersion = 100;.
  • 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, обозначает то что больше нет суперклассов, потому что мы достигли верха иерархии классов.

До этого алгоритм сериализации записывал описание классов ассоциированных с объектом и всех его суперклассов. Теперь будут записаны фактические данные ассоциированные с объектом. Сначала записываются члены класса parent:
  • 00 00 00 0A: 10, Значение parentVersion.

Дальше перемещаемся в SerialTest
  • 00 00 00 42: 66, Значение version.

Следующие несколько байт очень интересны. Алгоритму необходимо записать информацию объекте класса contain.

Листинг 8.
contain con = new contain();

* This source code was highlighted with Source Code Highlighter.

Алгоритм сериализации ещё не записал описание класса contain. Настал момент это сделать.
  • 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: Число полей в классе.

Алгоритм должен записать описание единственного поля класса conatin, int containVersion = 11;.
  • 0x49: Код типа поля. 49 обозначает «I», которое закреплено за Int.
  • 00 0E: Длина имени поля.
  • 63 6F 6E 74 61 69 6E 56 65 72 73 69 6F 6E: containVersion, имя поля.
  • 0x78: TC_ENDBLOCKDATA, конец опционального блока данных для объекта.

Дальше алгоритм проверяет, имеет ли contain родительский класс. Если имеет, то алгоритм начинает запись этого класса; но в нашем случае суперкласса у contain нету, и алгоритм записывает TC_NULL.
  • 0x70: TC_NULL

В конце алгоритм записывает фактические данные ассоциированные с объектом класса conatin.
  • 00 00 00 0B: 11, значение containVersion.

Заключение


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

Об авторе


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

Ссылки


Java object serialization specification. (Spec is a PDF.)
«Flatten your objects: Discover the secrets of the Java Serialization API» (Todd M. Greanier, JavaWorld, July 2000).
Chapter 10 of Java RMI (William Grosso, O'Reilly, October 2001).
Перевод: Sathiskumar Palaniappan
Николай @VampiRUS
карма
19,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (52)

  • –3
    Сериализация, имхо, вымирающее явление. Гораздо удобнее хранить объекты, скажем в XML — это удобочитаемо, просто и понятно. Тут нам на помощь приходит стандарт JAXB.

    Кстати непонятно, почему еще в пятой Джаве не сделали аннотацию для Serializable.
    • +1
      Удобнее — да, а по скорости загрузки-выгрузки Serializable решает. Хотя все тоже имхо и зависит от конкретного случая.
      • 0
        Сейчас плюс ко всему очень активно развивается SOA и соответственно технологии веб-сервисов — тут уж выбирать не приходится — только XML.

        Сериализация в Джаве опять же работает только для Джавы, а парсить XML можно на любом языке.
        • +4
          Не хочу вас обидеть, но, по-моему, вы напрасно объединяете веб-сервисы, XML и SOA
          • 0
            Веб-сервис — наиболее распространенная структурная единица SOA, интерфейсы и взаимодействие веб-сервисов осуществляется на XML (WSDL и SOAP соответственно). Где подвох?
            • 0
              Подвоха нет. Есть следующая неточность:
              >> интерфейсы и взаимодействие веб-сервисов осуществляется на XML,
              справедливая только в том случае, если это WSDL/SOAP-сервис.

              SOAP — частный и вынужденный случай сериализации объектов (в XML). XML-RPC, из которого торчат ноги SOAP, в урезанном случае, может работать и без него (без сериализации объектов в XML, я имею в виду).

              WSDL и WS-* — частный случай SOA. В общем случае существует и REST-web-service, и менее «мусорный» JSON-форматтер, так что выбирать есть из чего, необязательно XML-сериализация.

              Как-то так.
        • 0
          Hessian безо всяких XML работает, более удобен чем SOAP и уж в связке с .Net точно работоспособен.
    • 0
      А разве нельзя сериализировать в XML? Или это чем-то отличается?
      • 0
        Реализация другая бужет же. Или я что-то не понял в вашем вопросе?
        • 0
          FlashXL выше сказал, что сериализация вымирающее явление, так как не в XML.
          • 0
            Ой. Не заметил, что второго уровня комментарий.
      • 0
        Конечно можно. Можно сериализовать в какой хотите формат. Суть сериализации в том, чтобы потом вы могли ваш объект потом десереализовать и в итоге получилось тоже самое, что было в начале.
        • 0
          Так почему же тогда сериализация — вымирающее явление?
          • 0
            Я такого никогда не утверждал. :) Это бред. Не слушайте никого, кто говорит такие вещи. Вот, прочтите мой комментарий ниже.
          • 0
            Я такого никогда не утверждал. :) Это бред. Не слушайте никого, кто говорит такие вещи. Вот, прочтите мой комментарий ниже.
      • 0
        simple.sourceforge.net/ вот этим можно.
      • 0
        Можно. Но не штатными средствами. Из нештатных наш выбор — xStream.
    • +4
      Сериализация (в программировании) — процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.

      XML-документ — это тоже последовательность битов. В статье объясняется принцип и для чего это вообще нужно. Так же рассматривается один из способов сериализации объектов — бинарный.
    • +9
      Один из хороших примеров использование сериализации — это когда sevlet-container шарит сессии между ему подобными контейнерами в кластере.
      В это случае от разработчика лишь требуется чтобы сессия была сериализуемой, то есть все объекты которые в нее складываются были сериализуемыми, и ваше приложение сразу готово к кластеризации.
      Вторая полезная штука — это сохранение сессий на диск между рестартами сервера (сервлет-контейнера), чтобы у всех пользователей не потерялся контекст и не пришлось заново логиниться, например.
      В данном случае XML совершенно не нужен. Я ни разу не заглядывал в бинарные файлы (или потоки) во что там превратилась моя сессия, ибо это совсем не нужно и неинтересно. И чем бинарней данные в этих случаях тем только лучше, потому что «производительность».
      По-моему, говоря об xml Вы имели ввиду не сериализацию в полном понимании этого слова, а лишь некоторый messaging.
    • 0
      А что сериализация нужна только для хранения? Пол JEE для обмена данными сериализацию использует. Там бинарный формат самый оптимальный.

    • +2
      По-моему, XML — это частный случай сериализации. Бинарная сериализация обычно используется для более компактного хранения и высокой производительности.

      Кстати WS* может использовать бинарный обмен данными между веб-сервисами для повышения производительности и экономии канала.

      Не знаком с JAXB, так что не могу ничего о нем сказать.
      • 0
        Да, в WS-* есть расширение MTOM, которое позволяет передавать большие объемы бинарных данных as is, не сериализуя посредством base64.
  • 0
    Статья интересная, я вот только не понял зачем такое подробное объяснение байт-кода? Разработчику это может понадобится? Или это только для широты кругозора?
    • 0
      Видимо для кругозора. В любом случае написано очень доступно, интересная статья.
    • 0
      Это может потребоваться если нужно написать сериализатор/десериализатор для других языков программирования (например С), тогда модули написанные на других языках смогут обмениваться инфой с модулями на Java.
  • 0
    Для моей сферической сериализации хватает GSON. Ничего писать не надо: вход — объект, выход — строка. И наоборот
    • –2
      Только наверное Json.
      • 0
        code.google.com/p/google-gson/
  • 0
    Я считаю что сериализацию использовать достаточно удобно — не нужны никакие сторонние классы, все просто и ясно.
    Для веб-сервисов — согласен, удобнее XML.
    • +1
      Все ж не стоит путать действие (сериализацию) и формат (XML, байт-код).
    • 0
      класы то сторонние не нужны, но нужно ручками загнать в поток каждое поле и потом считать его назад. В отдельных случаях, согласен, нужна тонкая работа, но в общем случае мне не интересно как оно будет передаваться: ХМЛ там, или СОА, или Жасон — хочется загнать весь объект в поток, чтоб на другом конце из потока достать — рутиная работа.
  • +1
    Я не очень хорошо знаком с Java, но у меня сразу, как только дело доходит до байт-кода, возникает вопрос: есть ли бинарная совместимость данных после такой сериализации? Например, между разными ОС, или между 32 и 64-битными машинами, или между разными версиями ява-машины, ну и т. д. Просто если ее нет, то об этом следует говорить, чтобы люди выбирали XML или что-нибудь еще для передачи данных.
    • +1
      Пять раз уже повторили что сериализация и хранение объекта в виде XML это одно, и то же, — сериализация — общее название преобразования объекта в базовую структуру — массив байтов.
      XML сериализация практически тоже самое, но для человека она более удобна для разбора, никакого более функционала не несёт, и никакой совместимости не добавит.
      Для мультиплатформенного переноса объектов естественно придется поискать или написать адаптеры, что для двоично-сериализованных данных, что для xml-сериализованных.
      • 0
        Хэй, откуда агрессия?
        а) Один из этих 5 раз про сериализацию это повторил я. Вообще не понял, к чему тут это ваше замечание.
        б) Насчет XML Вы неправы. Если данные сохранить как "123", то они везде прочитаются безо всяких адаптеров, а если сохранять в байт-коде, то это может быть «7B», «007B», «7B00», «0000007B» и т.д.
    • +2
      Есть совместимость, не волнуйтесь. Между разными ОС и архитектурами точно (в Java размеры типов int, long и т.д. фиксированы). А для совместимости между разными версиями самой Java, видимо, и используется поле STREAM_VERSION (версия сериализации).
      • 0
        Вот, что-то такое я и надеялся услышать, спасибо, прояснили вопрос. Я так-то не волнуюсь) К миру Java отношения имею мало. Просто в этом месте часто грабли бывают. Хорошо, что тут их нет вроде бы.
      • 0
        а не для версии ли сериализуемого объекта? ниже по треду сказали, что зря не вспомнили serialVersionUID. как раз для того и нужно, чтобы мы десериализатором более новой версии объекта не пытались разобрать байт-представление старой версии и наоборот.
        • 0
          Для версии самого объекта (точнее, класса) — как раз используется serialVersionUID, но это отдельное поле.
      • 0
        > в Java размеры типов int, long и т.д. фиксированы
        Некропост, но между x32 и x64 архитектурами есть большая разница. int кратен размеру слова. Т.е 4 байта для x32 систем и 8 байт для x64.
        Чего не могу сказать, дак это совместимости сериализации между ними. Пока такой задачи не возникало. Доверюсь Sun/Oracle и буду надеяться, что это обеспечивается версией сериализации.
    • +1
      Очень дельное замечание. Я как-то мимо ушей пропустил вопрос с платформенной переносимостью. Big-Endian, Little-Endian всякие бывают. Автор как-то упустил этот момент. Но вот уже пояснили, что переносимость действительно есть.
  • +4
    По моему вы незаслуженно забыли рассказать о serialVersionUID.
    • 0
      Обсолютно согласен. Для специалиста с 4х летним стажем это непростительное упущение.
      Так же описание того, что сериализация нужна для взаимодействия между системами является, я считаю, не совсем правильным. Взаимодействиями между системами лучше осуществлять основываясь на других принципах.

      А вот пример реального применения: Останов enterprise сервера, на котором крутятся несколько тысяч логических процессов.
      • 0
        Абсолютно =))
        • 0
          «Для специалиста с 4х летним стажем это непростительное упущение» =)
      • 0
        А разве это не получится взаимодействие двух идентичных систем, просто разрозненных во времени? То что остановили и то что потом поднялось из дампа.
  • +1
    Скажите правильно ли я понял, что одно из применений механизма сериализации — это автоматизация процесса построения протоколов для взаимодействия приложений по сети? При этом данное взаимодействие может быть сколь угодно сложным за счёт того, что тип объекта и его данные однозначно востанавливаются на другом сервисе. И хотелось бы узнать какой подход применяется чаще при разработке в среде Java: разработка своего протокола или использование механизма сериализации? Так же насколько я понял допустимо использование смешенного подхода, когда часть данных передаётся в текстовом вде (например xml), а чать в виде байткода.
    • +1
      У меня данные бегают в JSON между серверами, взаимодействуют Java-демоны и Веб-сервер с PHP. Очень удобно, и быстро работает.
    • 0
      В среде Java, как и во многих других средах, основным подходом сейчас являются веб-сервисы.
  • 0
    «и содержит объект контейнер contain» как-то шиворот-навыворот получились :)
    • 0
      спасибо, подправил
  • +1
    Как-то натыкался на забавную штуку — incubator.apache.org/thrift/ Возможно кому-то будет интересно почитать.
  • 0
    я конечно понимаю, что тут в основном веб разработчики сидят, но не забывайте, что java язык не только для веба. Поэтому серилизация — это мощнейший инструмент для передачи данных между серверами, rmi, java baen'ов и т.д. И ни о каких xml даже речи не идет, когда надо производительность.
    Статья очень скудная, скорее описывает протокол(хоят это почитать было интересно).
    Ни слова не сказано про подводные камни, и тоности, даже про ключевое слово transient ни слова.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.