JavaRush /Java-Blog /Random-DE /Serialisierung wie sie ist. Teil 2
articles
Level 15

Serialisierung wie sie ist. Teil 2

Veröffentlicht in der Gruppe Random-DE

Leistung

Wie ich bereits sagte, funktioniert die Standard-Serialisierung über die Reflection-API. Das bedeutet, dass für die Serialisierung die Klasse des zu serialisierenden Objekts verwendet wird, eine Liste von Feldern daraus entnommen wird und verschiedene Bedingungen für alle Felder in der Schleife überprüft werden ( vorübergehend oder nicht, wenn es sich um ein Objekt handelt, dann Externalisierbar oder Serialisierbar ). Die Werte werden in den Stream geschrieben und auch per Reflektion aus den Feldern abgerufen ... Im Allgemeinen ist die Situation klar. Im Gegensatz zu dieser Methode wird der gesamte Ablauf bei der Verwendung der erweiterten Serialisierung vom Entwickler selbst gesteuert. Es bleibt abzuwarten, welche Geschwindigkeitsvorteile dies mit sich bringt. Also die Testbedingungen. Ein Objekt beliebiger Struktur. Zwei Optionen – eine Serializable und die zweite Externalizable . Eine bestimmte Anzahl von Objekten beider Optionen wird mit beliebigen Daten (identisch für jedes Objektpaar) initialisiert und dann in einem Container abgelegt. Der Container ist in einem Fall auch serialisierbar und in einem anderen Fall externalisierbar . Als nächstes werden die Container mit Zeitmessungen serialisiert und deserialisiert. Den vollständigen Testcode zusammen mit der Build- Datei für Ant finden Sie hier – serialization.zip (Sie können ihn von der Quellseite herunterladen). Im Text werde ich nur Auszüge wiedergeben. Das serialisierbare Objekt enthält den folgenden Satz von Feldern: private int fieldInt; private boolean fieldBoolean; private long fieldLong; private float fieldFloat; private double fieldDouble; private String fieldString; Der Test enthält drei Implementierungen von externalisierbaren Containern. Der erste, ContainerExt1 , ist der einfachste. Dies ist einfach eine Serialisierung der enthaltenen java.util.List- Objekte : Die zweite Implementierung, ContainerExt2 , serialisiert alle vorhandenen Objekte der Reihe nach und stellt ihnen die Anzahl der Objekte voran: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-методы ein Objektов: 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-файле задано количество создаваемых ein Objektов – 100000. Другое количество может быть задано с помощью параметра командной строки -Dobjcount= . Итак, Wieовы результаты выполнения теста? На 100000 создаваемых ein Objektов (результаты могут незначительно отличаться от запуска к запуску): 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 Was мы видим? Первый способ реализации Externalizable даже несколько хуже стандартной сериализации. Сериализация занимает немного больше времени, десериализация сравнима. Размеры файлов тоже немного в пользу стандартной сериализации. Вывод – простейшая сериализация контейнера преимуществ не дает: +15% при сериализации, десериализация отличается на доли процента, причем Wie в одну, так и в другую сторону. Второй способ реализации Externalizable по характеристикам практически идентичен первому. Чуть быстрее сериализация, но все равно проигрывает стандартной, десериализация чуть выигрывает. Размер Datei практически идентичен первому способу (разница – 38 Byte). Выигрыша по сравнению со стандартной сериализацией нет – +10% при сериализации, -8% при десериализации. Третий способ реализации Externalizable. Вот тут есть на что посмотреть! Сериализация быстрее в 15 раз! Естественно, плюс-минус, но тем не менее – разница на порядок! Десериализация быстрее практически в 11 раз! Unterschied тоже на порядок! Опять же плюс-минус, но мне не удавалось получить разницу меньше, нежели в 5 раз. Ну и разница в размере Datei -13%. Как маленькое, но приятное дополнение. Думаю, комментарии излишни. Получаемые от грамотной реализации Externalizable преимущества в скорости с лихвой компенсируют затраты на эту самую реализацию. Грамотной – в смысле, целиком и völlig реализованной самостоятельно, без использования имеющихся механизмов сериализации целых ein Objektов (в основном это методы writeObject/readObject). Использование же имеющихся механизмов и/oder смешивание со стандартной сериализацией способно свести скоростные преимущества Externalizable на нет. Однако есть и ...

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

И прежде всего это нарушение Integrität графа. Поскольку протокол сериализации не используется – контроль Integrität остается на самом разработчике. И об этом следует помнить, ибо в некоторых случаях можно легко убить все преимущества. Если, к примеру, необходимо сериализовать очень много экземпляров класса A, каждый из которых ссылается на единственный экземпляр класса B, то при неумелом использовании Externalizable может получиться так, что экземпляр B будет сериализован по разу на каждый экземпляр A, что даст потерю Wie в скорости, так и в объеме сериализованных данных. А при десериализации мы вообще получим кучу экземпляров B anstatt одного! Was намного хуже. Поэтому, да и не только, Externalizable следует использовать обдуманно. Как, впрочем, и любую другую возможность. Если необходимо сериализовать достаточно сложные графы – пожалуй, лучше все-таки воспользоваться имеющимися механизмами. Если же объемы данных большие, но сложность невелика – можно немного поработать и получить солидный выигрыш в скорости. В любом случае лучше написать небольшой прототип и уже на нем оценивать реальную Geschwindigkeit и сложность реализации Integrität. Перейдем к следующему вопросу, связанному с сериализацией.

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

Есть такое правило: проверять входящие данные (входные параметры функций и т.п.) на "правильность" – соответствие определенным требованиям. Причем это не столько правило хорошего тона, сколько правило выживания Anwendungen. Ибо если этого не сделать, то при передаче неверных параметров в лучшем случае (действительно – в лучшем!) приложение просто "упадет". В худшем случае оно тихо примет предложенные данные и может нанести значительно больший урон. Про это правило худо-бедно, но помнят. Однако конструкторы и открытые методы – не единственный способ поставки данных ein Objektу. Точно так же ein Objekt может быть создан с помощью десериализации. И вот тут о контроле внутреннего состояния полученного ein Objektа, Wie правило, забывают. Между тем, создать поток для получения из него ein Objektа с неверным внутренним состоянием не легко, а очень легко. Пример номер один. Объект с двумя полями типа java.util.Date. Одно поле – начало интервала времени, другое – конец. Следовательно, между ними должно существовать определенное соотношение (конец должен быть не раньше начала). Однако любой человек, знающий ByteCode, сумеет отредактировать сериализованный ein Objekt так, что после десериализации конец интервала будет раньше начала. К чему приведет появление в системе такого ein Objektа – предугадать сложно. В любом случае, ничего хорошего ждать не приходится. Потому, примите для себя...
Правило 1. После десериализации ein Objektа необходимо проверить его внутреннее состояние (инварианты) на правильность, точно так же, Wie и при создании с помощью конструктора. Если ein Objekt не прошел такую проверку, необходимо инициировать исключение java.io.InvalidObjectException.
Пример номер два. Объект класса A содержит в себе private-поле типа java.util.Date. Для изменения снаружи ein Objektа это поле недоступно. Однако возможна следующая операция: к потоку дописывается некоторая информация. Потом, после десериализации из этого потока ein Objektа класса A производится десериализация еще одного ein Objektа, но уже типа Date. Как мы уже видели в примере ранее, можно создать такой поток (в примере он создавался легально), что при десериализации этот второй ein Objekt в действительности будет лишь ссылкой на экземпляр Date, казалось бы так надежно спрятанный внутри ein Objektа класса A. Соответственно, с этим экземпляром можно делать все, что заблагорасудится.
Не буду вдаваться в подробности. Описание этого приема есть в книге Джошуа Блох. Java. Эффективное программирование, в статье 56. Скажу только, что достаточно к потоку дописать 5 Byte, чтобы добиться желаемого.
Wasбы этого избежать, необходимо следовать следующему правилу:
Правило 2. Если в составе класса A присутствуют ein Objektы, которые не должны быть доступными для изменения извне, то при десериализации экземпляра класса A необходимо anstatt этих ein Objektов создать и сохранить их копии.
Приведенные выше примеры показывают возможные "дыры" в безопасности. Следование упомянутым правилам, разумеется, не спасает от проблем, но может существенно снизить их количество. Советую по этому поводу почитать книгу Джошуа Блох. Java. Эффективное программирование, статью 56. Ну и последняя тема, которой я хотел бы коснуться –

Сериализация ein Objektов Singleton

Тех, кто не в курсе, что такое Singleton, отсылаю к отдельной статье. В чем проблема сериализации Singleton-ов? А проблема в уже упомянутом мной факте – после десериализации мы получим другой ein Objekt. Это видно в результатах первого из тестов в этой статье – ссылки на исходный и десериализованный ein Objektы не совпадают. Таким образом, сериализация дает возможность создать Singleton еще раз, что нам совсем не нужно. Можно, конечно, запретить сериализовать Singleton-ы, но это, фактически, уход от проблемы, а не ее решение. Решение же заключается в следующем. В классе определяется метод со следующей сигнатурой ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException Модификатор доступа может быть private, protected и по умолчанию (default). Можно, наверное, сделать его и public, но смысла я в этом не вижу. НаBedeutung этого метода – возвращать замещающий ein Objekt anstatt ein Objektа, на котором он вызван. Приведу простой пример: 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. Соответственно, именно эти два значения и должны фигурировать после десериализации. Was делается в методе readResolve? Он вызывается на десериализованном ein Objektе. И возвращать он должен уже существующий экземпляр класса, соответствующий внутреннему состоянию десериализованного ein Objektа. В данном примере – проверяется Bedeutung поля answer. Если ein Objekt, соответствующий внутреннему состоянию, не найден... На мой взгляд, это зависит от ситуации. В приведенном примере стоит инициировать исключение. Возможно, в Wieих-то ситуациях будет полезно вернуть this. Примером этого, например, является реализация java.util.logging.Level. Существует и обратный метод – writeReplace, который, Wie вы, наверное, уже догадались, позволяет выдать замещающий ein Objekt anstatt текущего, для сериализации. Мне, честно сказать, трудно представить себе ситуации, в которых это может понадобиться. Хотя в недрах Codeа Sun он Wie-то используется. Оба метода, Wie readResolve, так и writeReplace, вызываются при использовании стандартных средств сериализации (методов readObject и writeObject), вне зависимости от того, объявлен ли сериализуемый класс Wie Serializable oder Externalizable. Самое интересное, что, похоже, из этих методов можно возвращать не только экземпляр класса, в котором этот метод определен, но и экземпляр другого класса. Я видел подобные примеры в глубинах библиотек Sun, во всяком случае, для writeReplace – точно видел. Но по Wieим принципам можно это делать – не берусь пока судить. Вообще, советую интересующимся просмотреть исходники J2SE 5.0, причем полные. Они доступны по лицензии JRL. Там есть много интересных примеров использования этих методов. Исходники можно взять тут – http://java.sun.com/j2se/jrl_download.html. Правда, требуется регистрация, но она, естественно, бесплатна. Отдельно хочу коснуться сериализации перечислений (enum), появившихся в Java 5.0. Поскольку при сериализации в поток пишется Name Element и его порядковый номер в определении в классе, можно было бы ожидать проблем при десериализации в случае изменения порядкового номера (что может случиться очень легко – достаточно поменять элементы местами). Однако, к счастью, таких проблем нет. Десериализация ein Objektов типа enum контролируется для обеспечения соответствия десериализуемых экземпляров уже имеющимся у виртуальной машины. Фактически, это то, что делает обычно метод readResolve, но реализовано где-то существенно глубже. Сопоставление ein Objektов осуществляется по имени. Разработчикам версии 5.0 – респект! * * * Наверное, на текущий момент это все, что я хотел рассказать о сериализации. Думаю, теперь она не кажется такой простой, Wieой казалась до прочтения этой статьи. И хорошо. Пребывание в блаженном неведении к добру не приводит. Ссылка на первоисточник: http://www.skipy.ru/technics/serialization.html
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION