Посмотрите механизм сериализации Java

Java задняя часть API Tomcat
Посмотрите механизм сериализации Java

Обзор

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

Операции сериализации в Java должны реализовывать интерфейс Serializable или Externalizable.

Роль сериализации

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

Пример сериализации

FileOutputStream f = new FileOutputStream("tmp.o");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("test");
s.writeObject(new ArrayList());
s.flush();

Обычный способ его использования — прямая запись объекта в поток.Например, в приведенном выше примере создается объект FileOutputStream, который выводится в файл tmp.o, а затем создается объект ObjectOutputStream для вложения предыдущий выходной поток. Сериализация может быть выполнена, когда мы вызываем метод writeObject.

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

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

пример десериализации

FileInputStream in = new FileInputStream("tmp.o");
ObjectInputStream s = new ObjectInputStream(in);
String test = (String)s.readObject();
List list = (ArrayList)s.readObject();

Для сериализации существует операция десериализации.Чтобы прочитать объект непосредственно через поток, сначала создайте объект FileInputStream, соответствующий входной файл которого tmp.o, а затем создайте объект ObjectInputStream, вложенный перед входным потоком, а затем вызовите метод Метод readObject для чтения объекта.

Процесс вызова метода readObject для десериализации операции не только восстановит сам объект, но и пройдет весь полный граф объектов и создаст все объекты, содержащиеся во всем графе объектов.

Какая польза от serialVersionUID

Во время операций сериализации часто можно увидеть, что класс, реализующий интерфейс Serializable, будет иметь атрибут serialVersionUID, а это статическая переменная с фиксированным значением. Например, что делает этот атрибут? На самом деле он в основном используется для проверки согласованности версий.У каждого класса есть такой ID, который будет совместно записываться в поток при сериализации, а затем выниматься и выполняться со значением serialVersionUID текущего класса при десериализации.Если два совпадают, версия непротиворечива и может быть успешно сериализована, но если они разные, сериализация завершается ошибкой.

private static final long serialVersionUID = -6849794470754667710L;

В общем, мы можем определить значение serialVersionUID сами или IDE может автоматически сгенерировать его за нас, и если мы явно не определяем serialVersionUID, это не значит, что serialVersionUID не существует, а генерируется для нас JDK. будет использовать имя класса, модификаторы класса, имена интерфейсов, поля, статическую информацию об инициализации, информацию о конструкторе, имена методов, модификаторы методов, сигнатуры методов и т. д., а дайджест, сгенерированный алгоритмом SHA, является окончательным значением serialVersionUID.

Что происходит, когда родительский класс сериализуется

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

Кроме того, если родительский класс не реализует интерфейс Serializable, объект, сгенерированный десериализацией, снова вызовет конструктор родительского класса, чтобы завершить инициализацию родительского класса. Поэтому начальное значение атрибута родительского класса обычно является значением типа по умолчанию. Например, атрибуты класса «Отец» не будут участвовать в сериализации, а значение атрибутов объекта «Отец» при десериализации — значение по умолчанию, равное 0.

public class Father {
	public int f;

	public Father() {
	}
}

public class Son extends Father implements Serializable {
	public int s;

	public Son() {
		super();
	}
}

Какие поля сериализуются

Какие поля класса будут участвовать в сериализации при сериализации? На самом деле есть два способа решить, какие поля будут сериализованы:

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

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

Как использовать ObjectStreamField? Например, как показано ниже, класс A имеет два поля, имя и пароль, и только поле имени сериализуется через объявление массива ObjectStreamField. При таком способе объявления не нужно беспокоиться о том, почему это так, это просто соглашение.

public class A implements Serializable {
    String name;
    String password

    private static final ObjectStreamField[] serialPersistentFields
                 = {new ObjectStreamField("name", String.class)};
 }

Сериализация типов перечисления

Сериализация типов Enum отличается от сериализации обычных классов Java, поэтому, прежде чем углубляться, вы можете прочитать эту статью, чтобы узнать больше о перечислении, "Понимание перечисления enum с точки зрения JDK".

Итак, мы знаем, что перечисление станет классом, который наследует java.lang.Enum после компиляции, а элементы в перечислении объявлены как static final, и будет сгенерирован статический блок кода static{}, и, наконец, значения и valueOf будут сгенерированы двумя методами. Класс Enum — это абстрактный класс с двумя основными атрибутами: именем и порядковым номером, которые используются для представления имени элемента перечисления и индекса позиции элемента перечисления соответственно.

Когда тип Enum участвует в сериализации, будет записан только атрибут имени в объекте перечисления, а другие атрибуты участвовать не будут. При десериализации сначала считывается атрибут name, а затем через метод valueOf класса java.lang.Enum находится соответствующий тип перечисления.

Кроме того, сериализацию типов Enum нельзя настроить, поэтому при сериализации будут игнорироваться такие методы, как writeObject, readObject, readObjectNoData, writeReplace и readResolve, а также свойства serialPersistentFields и serialVersionUID.

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

Роль внешнего интерфейса

Интерфейс Externalizable в основном предназначен для предоставления пользователям возможности самостоятельно управлять сериализованным содержимым.Хотя мы также видели, что переходные процессы и ObjectStreamField могут определять сериализованные поля, интерфейс Externalizable может быть более гибким. Вы можете видеть, что он на самом деле наследует интерфейс Serializable и предоставляет два метода, writeExternal и readExternal, которые управляют содержимым сериализации и десериализации в этих двух методах.

public interface Externalizable extends java.io.Serializable {
    
    void writeExternal(ObjectOutput out) throws IOException;

    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Например, в следующем примере мы можем дополнительно записать объект Date в методе writeExternal, а затем записать значение value. Соответственно при десериализации объект Date и значение считываются в методе readExternal. На этом пользовательская операция сериализации завершена.

public class ExternalizableTest implements Externalizable {
	public String value = "test";

	public ExternalizableTest() {
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		Date d = new Date();
		out.writeObject(d);
		out.writeObject(value);
	}

	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		Date d = (Date) in.readObject();
		System.out.println(d);
		System.out.println((String) in.readObject());
	}

}

заменить объект при записи

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

class Person implements Serializable {
	private String name;
	private int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	private Object writeReplace() throws ObjectStreamException {
		Object[] properties = new Object[2];
		properties[0] = name;
		properties[1] = age;
		return properties;
	}
}
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.o"));
Object[] properties = (Object[]) ois.readObject();

заменить объект при чтении

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

class Person implements Serializable {
	private String name;
	private int age;

	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	private Object readResolve() throws ObjectStreamException {
		return 2222;
	}
}
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.o"));
Object o = ois.readObject();

------------- Рекомендуем прочитать ------------

Понимание перечисления enum с точки зрения JDK

Резюме моей статьи за 2017 год — машинное обучение

Краткое изложение моих статей за 2017 год — Java и промежуточное ПО

Резюме моих статей 2017 года — глубокое обучение

Краткое изложение моих статей за 2017 год — исходный код JDK

Резюме моей статьи за 2017 год — обработка естественного языка

Резюме моих статей 2017 года — Java Concurrency

------------------рекламное время----------------

Меню официальной учетной записи было разделено на «распределенное», «машинное обучение», «глубокое обучение», «НЛП», «глубина Java», «ядро параллелизма Java», «исходный код JDK», «ядро Tomcat», и т.д. Там может быть один стиль, чтобы удовлетворить ваш аппетит.

Моя новая книга «Анализ проектирования ядра Tomcat» продана на Jingdong, и нуждающиеся друзья могут ее купить. Спасибо друзья.

Зачем писать «Анализ проектирования ядра Tomcat»

Добро пожаловать, чтобы следовать:

这里写图片描述