JavaRush /Java Blog /Random-TL /Serialization tulad nito. Bahagi 2
articles
Antas

Serialization tulad nito. Bahagi 2

Nai-publish sa grupo

Pagganap

Gaya ng sinabi ko na, gumagana ang karaniwang serialization sa pamamagitan ng Reflection API. Na nangangahulugan na para sa serialization, ang klase ng object na serialized ay kinuha, isang listahan ng mga patlang ay kinuha mula dito, iba't ibang mga kondisyon ay nasuri para sa lahat ng mga patlang sa loop ( lumilipas o hindi, kung isang bagay, pagkatapos ay Externalizable o Serializable ), ang mga halaga ay isinulat sa stream, at kinukuha din sila mula sa mga patlang sa pamamagitan ng pagmuni-muni ... Sa pangkalahatan, ang sitwasyon ay malinaw. Sa kaibahan sa pamamaraang ito, ang buong pamamaraan kapag gumagamit ng pinahabang serialization ay kinokontrol ng developer mismo. Ito ay nananatiling makikita kung ano ang mga pakinabang na ibinibigay nito sa mga tuntunin ng bilis. Kaya, ang mga kondisyon ng pagsubok. Isang bagay ng di-makatwirang istraktura. Dalawang opsyon - isa Serializable , ang pangalawang Externalizable . Ang isang tiyak na bilang ng mga bagay ng parehong mga pagpipilian ay sinisimulan sa arbitrary na data (magkapareho para sa bawat pares ng mga bagay) at pagkatapos ay inilagay sa isang lalagyan. Ang container ay Serializable din sa isang case at Externalizable sa isa pa . Susunod, ise-serialize at deserialized ang mga container sa mga sukat ng oras. Ang buong code ng pagsubok kasama ang build file para sa ant ay matatagpuan dito - serialization.zip (maaari mong i-download ito mula sa source site). Sa text ay bibigyan ko lamang ng mga sipi. Ang serializable object ay naglalaman ng mga sumusunod na hanay ng mga field: private int fieldInt; private boolean fieldBoolean; private long fieldLong; private float fieldFloat; private double fieldDouble; private String fieldString; Ang pagsubok ay naglalaman ng tatlong pagpapatupad ng Externalizable na mga container. Ang una, ContainerExt1 , ay ang pinakasimple. Isa lang itong serialization ng naglalaman ng java.util.List objects : Ang pangalawang pagpapatupad, ContainerExt2 , ay nagse-serialize ng lahat ng umiiral na object nang sunud-sunod, na inilalagay ang mga ito sa bilang ng mga object:public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(items); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { items = (List )in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(items.size()); for(Externalizable ext : items) out.writeObject(ext); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int count = in.readInt(); for(int i=0; i Третья реализация, ContainerExt3, использует externalizable-методы an objectов: public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(items.size()); for(Externalizable ext : items) ext.writeExternal(out); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int count = in.readInt(); for(int i=0; i Запускается тест с помощью команды ant (поскольку задача run запускается по умолчанию). В build-файле задано количество создаваемых an objectов – 100000. Другое количество может быть задано с помощью параметра командной строки -Dobjcount= . Итак, Howовы результаты выполнения теста? На 100000 создаваемых an objectов (результаты могут незначительно отличаться от запуска к запуску): Creating 100000 objects Serializable: written in 3516ms, readed in 3235 Externalizable1: written in 4046ms, readed in 3234 Externalizable2: written in 3875ms, readed in 2985 Externalizable3: written in 235ms, readed in 297 И размеры сериализованных данных (размеры файлов на диске): cont.ser 5 547 955 contExt1.ser 5 747 884 contExt2.ser 5 747 846 contExt3.ser 4 871 461 What мы видим? Первый способ реализации Externalizable даже несколько хуже стандартной сериализации. Сериализация занимает немного больше времени, десериализация сравнима. Размеры файлов тоже немного в пользу стандартной сериализации. Вывод – простейшая сериализация контейнера преимуществ не дает: +15% при сериализации, десериализация отличается на доли процента, причем How в одну, так и в другую сторону. Второй способ реализации Externalizable по характеристикам практически идентичен первому. Чуть быстрее сериализация, но все равно проигрывает стандартной, десериализация чуть выигрывает. Размер file практически идентичен первому способу (разница – 38 byte). Выигрыша по сравнению со стандартной сериализацией нет – +10% при сериализации, -8% при десериализации. Третий способ реализации Externalizable. Вот тут есть на что посмотреть! Сериализация быстрее в 15 раз! Естественно, плюс-минус, но тем не менее – разница на порядок! Десериализация быстрее практически в 11 раз! Difference тоже на порядок! Опять же плюс-минус, но мне не удавалось получить разницу меньше, нежели в 5 раз. Ну и разница в размере file -13%. Как маленькое, но приятное дополнение. Думаю, комментарии излишни. Получаемые от грамотной реализации Externalizable преимущества в скорости с лихвой компенсируют затраты на эту самую реализацию. Грамотной – в смысле, целиком и fully реализованной самостоятельно, без использования имеющихся механизмов сериализации целых an objectов (в основном это методы writeObject/readObject). Использование же имеющихся механизмов и/or смешивание со стандартной сериализацией способно свести скоростные преимущества Externalizable на нет. Однако есть и ...

Обратная сторона медали

И прежде всего это нарушение integrity графа. Поскольку протокол сериализации не используется – контроль integrity остается на самом разработчике. И об этом следует помнить, ибо в некоторых случаях можно легко убить все преимущества. Если, к примеру, необходимо сериализовать очень много экземпляров класса A, каждый из которых ссылается на единственный экземпляр класса B, то при неумелом использовании Externalizable может получиться так, что экземпляр B будет сериализован по разу на каждый экземпляр A, что даст потерю How в скорости, так и в объеме сериализованных данных. А при десериализации мы вообще получим кучу экземпляров B instead of одного! What намного хуже. Поэтому, да и не только, Externalizable следует использовать обдуманно. Как, впрочем, и любую другую возможность. Если необходимо сериализовать достаточно сложные графы – пожалуй, лучше все-таки воспользоваться имеющимися механизмами. Если же объемы данных большие, но сложность невелика – можно немного поработать и получить солидный выигрыш в скорости. В любом случае лучше написать небольшой прототип и уже на нем оценивать реальную speed и сложность реализации integrity. Перейдем к следующему вопросу, связанному с сериализацией.

Безопасность данных

Есть такое правило: проверять входящие данные (входные параметры функций и т.п.) на "правильность" – соответствие определенным требованиям. Причем это не столько правило хорошего тона, сколько правило выживания applications. Ибо если этого не сделать, то при передаче неверных параметров в лучшем случае (действительно – в лучшем!) приложение просто "упадет". В худшем случае оно тихо примет предложенные данные и может нанести значительно больший урон. Про это правило худо-бедно, но помнят. Однако конструкторы и открытые методы – не единственный способ поставки данных an objectу. Точно так же an object может быть создан с помощью десериализации. И вот тут о контроле внутреннего состояния полученного an object, How правило, забывают. Между тем, создать поток для получения из него an object с неверным внутренним состоянием не легко, а очень легко. Пример номер один. Объект с двумя полями типа java.util.Date. Одно поле – начало интервала времени, другое – конец. Следовательно, между ними должно существовать определенное соотношение (конец должен быть не раньше начала). Однако любой человек, знающий bytecode, сумеет отредактировать сериализованный an object так, что после десериализации конец интервала будет раньше начала. К чему приведет появление в системе такого an object – предугадать сложно. В любом случае, ничего хорошего ждать не приходится. Потому, примите для себя...
Правило 1. После десериализации an object необходимо проверить его внутреннее состояние (инварианты) на правильность, точно так же, How и при создании с помощью конструктора. Если an object не прошел такую проверку, необходимо инициировать исключение java.io.InvalidObjectException.
Пример номер два. Объект класса A содержит в себе private-поле типа java.util.Date. Для изменения снаружи an object это поле недоступно. Однако возможна следующая операция: к потоку дописывается некоторая информация. Потом, после десериализации из этого потока an object класса A производится десериализация еще одного an object, но уже типа Date. Как мы уже видели в примере ранее, можно создать такой поток (в примере он создавался легально), что при десериализации этот второй an object в действительности будет лишь ссылкой на экземпляр Date, казалось бы так надежно спрятанный внутри an object класса A. Соответственно, с этим экземпляром можно делать все, что заблагорасудится.
Не буду вдаваться в подробности. Описание этого приема есть в книге Джошуа Блох. Java. Эффективное программирование, в статье 56. Скажу только, что достаточно к потоку дописать 5 byte, чтобы добиться желаемого.
Whatбы этого избежать, необходимо следовать следующему правилу:
Правило 2. Если в составе класса A присутствуют an objectы, которые не должны быть доступными для изменения извне, то при десериализации экземпляра класса A необходимо instead of этих an objectов создать и сохранить их копии.
Приведенные выше примеры показывают возможные "дыры" в безопасности. Следование упомянутым правилам, разумеется, не спасает от проблем, но может существенно снизить их количество. Советую по этому поводу почитать книгу Джошуа Блох. Java. Эффективное программирование, статью 56. Ну и последняя тема, которой я хотел бы коснуться –

Сериализация an objectов Singleton

Тех, кто не в курсе, что такое Singleton, отсылаю к отдельной статье. В чем проблема сериализации Singleton-ов? А проблема в уже упомянутом мной факте – после десериализации мы получим другой an object. Это видно в результатах первого из тестов в этой статье – ссылки на исходный и десериализованный an objectы не совпадают. Таким образом, сериализация дает возможность создать Singleton еще раз, что нам совсем не нужно. Можно, конечно, запретить сериализовать Singleton-ы, но это, фактически, уход от проблемы, а не ее решение. Решение же заключается в следующем. В классе определяется метод со следующей сигнатурой ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException Модификатор доступа может быть private, protected и по умолчанию (default). Можно, наверное, сделать его и public, но смысла я в этом не вижу. Наmeaning этого метода – возвращать замещающий an object instead of an object, на котором он вызван. Приведу простой пример: public class Answer implements Serializable{ private static final String STR_YES = "Yes"; private static final String STR_NO = "No"; public static final Answer YES = new Answer(STR_YES); public static final Answer NO = new Answer(STR_NO); private String answer = null; private Answer(String answer){ this.answer = answer; } private Object readResolve() throws ObjectStreamException{ if (STR_YES.equals(answer)) return YES; if (STR_NO.equals(answer)) return NO; throw new InvalidObjectException("Unknown value: " + answer); } } Класс, приведенный выше – простейший перечислимый тип. Всего два значения – Answer.YES и Answer.NO. Соответственно, именно эти два значения и должны фигурировать после десериализации. What делается в методе readResolve? Он вызывается на десериализованном an objectе. И возвращать он должен уже существующий экземпляр класса, соответствующий внутреннему состоянию десериализованного an object. В данном примере – проверяется meaning поля answer. Если an object, соответствующий внутреннему состоянию, не найден... На мой взгляд, это зависит от ситуации. В приведенном примере стоит инициировать исключение. Возможно, в Howих-то ситуациях будет полезно вернуть this. Примером этого, например, является реализация java.util.logging.Level. Существует и обратный метод – writeReplace, который, How вы, наверное, уже догадались, позволяет выдать замещающий an object instead of текущего, для сериализации. Мне, честно сказать, трудно представить себе ситуации, в которых это может понадобиться. Хотя в недрах codeа Sun он How-то используется. Оба метода, How readResolve, так и writeReplace, вызываются при использовании стандартных средств сериализации (методов readObject и writeObject), вне зависимости от того, объявлен ли сериализуемый класс How Serializable or Externalizable. Самое интересное, что, похоже, из этих методов можно возвращать не только экземпляр класса, в котором этот метод определен, но и экземпляр другого класса. Я видел подобные примеры в глубинах библиотек Sun, во всяком случае, для writeReplace – точно видел. Но по Howим принципам можно это делать – не берусь пока судить. Вообще, советую интересующимся просмотреть исходники J2SE 5.0, причем полные. Они доступны по лицензии JRL. Там есть много интересных примеров использования этих методов. Исходники можно взять тут – http://java.sun.com/j2se/jrl_download.html. Правда, требуется регистрация, но она, естественно, бесплатна. Отдельно хочу коснуться сериализации перечислений (enum), появившихся в Java 5.0. Поскольку при сериализации в поток пишется Name element и его порядковый номер в определении в классе, можно было бы ожидать проблем при десериализации в случае изменения порядкового номера (что может случиться очень легко – достаточно поменять элементы местами). Однако, к счастью, таких проблем нет. Десериализация an objectов типа enum контролируется для обеспечения соответствия десериализуемых экземпляров уже имеющимся у виртуальной машины. Фактически, это то, что делает обычно метод readResolve, но реализовано где-то существенно глубже. Сопоставление an objectов осуществляется по имени. Разработчикам версии 5.0 – респект! * * * Наверное, на текущий момент это все, что я хотел рассказать о сериализации. Думаю, теперь она не кажется такой простой, Howой казалась до прочтения этой статьи. И хорошо. Пребывание в блаженном неведении к добру не приводит. Ссылка на первоисточник: http://www.skipy.ru/technics/serialization.html
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION