Обязателен для Java-программистов: всесторонний анализ сериализации

Java

предисловие

Я считаю, что в своей повседневной разработке вы часто видите, что объект Java «реализует Serializable». Так какая от этого польза? В этой статье анализируется точка знания последовательности со следующих точек зрения ~

  • Что такое Java-сериализация?
  • Зачем нужна сериализация?
  • Использование сериализации
  • Общий API сериализации Java
  • использование сериализации
  • Сериализировать нижний слой
  • Точки сериализации ежедневных разработок
  • Сериализация Общие вопросы интервью

1. Что такое сериализация Java?

  • Сериализация: процесс преобразования объекта Java в последовательность байтов.
  • Десериализация: процесс восстановления последовательности байтов в объект Java.

Во-вторых, зачем вам сериализация?

Объекты Java запускаются в динамической памяти JVM, и если JVM остановится, ее жизнь резко прервется.

Что делать, если вы хотите сохранить эти объекты на диск или передать их по сети на другую удаленную машину после остановки JVM? Диски и другое оборудование не знают объекты Java, они знают только двоичные машинные языки, поэтому мы должны преобразовать эти объекты в массивы байтов.Этот процесс сериализации~

Если провести аналогию, то для кодового фермера, бродящего по большому городу, переезд является нормой. Когда мы перемещаем стол, стол слишком велик, чтобы пройти через меньшую дверь, поэтому нам нужно разобрать его и переместить внутрь. Процесс разборки стола — сериализация. А процесс восстановления (установки) стола - это десериализация.

3. Использование сериализации

Сериализация позволяет объектам существовать независимо от выполнения программы и преследует две основные цели:

  • 1) Механизм сериализации позволяет хранить объекты на жестком диске, что снижает нагрузку на память, а также играет роль в сохраняемости;

Например, объект сеанса на веб-сервере, когда более 100 000 пользователей получают одновременный доступ, может быть 100 000 объектов сеанса, и память может быть неусвоена, поэтому веб-контейнер сначала сериализует некоторые сеансы на жесткий диск и ждет Используйте, а затем восстанавливайте объекты, сохраненные на жестком диске, в память.

  • 2) Благодаря механизму сериализации передача Java-объектов по сети больше не является фантастикой.

Когда мы используем Dubbo для удаленного вызова сервисного фреймворка, нам нужно реализовать интерфейс Serializable для передаваемых Java-объектов, то есть сериализовать Java-объекты, потому что только таким образом можно передавать объекты по сети.

В-четвертых, часто используемый API для сериализации Java.

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable

Сериализуемый интерфейс

Интерфейс Serializable — это интерфейс маркера без методов или полей. После реализации этого интерфейса объекты этого класса помечаются как сериализуемые.

public interface Serializable {
}

Внешний интерфейс

Externalizable наследует интерфейс Serializable и определяет два абстрактных метода: writeExternal() и readExternal().Если разработчики используют Externalizable для реализации сериализации и десериализации, им необходимо переопределить методы writeExternal() и readExternal().

public interface Externalizable extends java.io.Serializable {
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

Класс java.io.ObjectOutputStream

Представляет поток вывода объекта, и его метод writeObject(Object obj) может сериализовать указанные параметры объекта obj, а затем записать полученную последовательность байтов в целевой поток вывода.

java.io.ObjectInputStream

представляет входной поток объекта, Его метод readObject() считывает последовательность байтов из входного потока, десериализует ее в объект и возвращает.

В-пятых, использование сериализации

Как используется сериализация? Давайте рассмотрим несколько ключевых моментов использования сериализации:

  • Объявите класс сущности, реализующий интерфейс Serializable.
  • Используйте метод writeObject класса ObjectOutputStream для реализации сериализации.
  • Используйте метод readObject класса ObjectInputStream для десериализации.

Объявите класс Student, который реализует Serializable

public class Student implements Serializable {

    private Integer age;
    private String name;

    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Используйте метод writeObject класса ObjectOutputStream для сериализации объекта Student.

После установки значения объекта Student записываем его в файл, то есть сериализуем, ха-ха~

ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("D:\\text.out"));
Student student = new Student();
student.setAge(25);
student.setName("jayWei");
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();

Взгляните на симпатичный внешний вид сериализации.Содержимое файла test.out выглядит следующим образом (открыть с помощью UltraEdit):

Используйте метод readObject класса ObjectInputStream для десериализации и повторного создания объекта ученика.

Затем прочитайте файл test.out и десериализуйте его в объект Student.

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student = (Student) objectInputStream.readObject();
System.out.println("name="+student.getName());

В-шестых, нижний уровень сериализации

Сериализуемый нижний слой

Интерфейс Serializable — это просто пустой интерфейс без методов и полей.Почему это так волшебно, что объект можно сериализовать, реализовав его?

public interface Serializable {
}

Чтобы проверить роль Serializable, удалите объект Student из приведенной выше демонстрации и реализуйте интерфейс Serializable и посмотрите, как идет процесс сериализации~

Во время сериализации возникло исключение. Информация о стеке выглядит следующим образом:

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:13)

Глядя на информацию о стеке, оказывается, что есть важные открытия, а именно:

Нижний слой такой:Когда ObjectOutputStream сериализуется, он определяет, какой тип объекта следует сериализовать, String? множество? перечисление? Или Serializable, если нет, выдайте NotSerializableException. Так что да,Сериализуемый на самом деле просто флаг, флаг сериализации~

написатьОбъект(Объект)

Метод сериализации — writeObject.Основываясь на приведенной выше демонстрации, давайте проанализируем волну его основной цепочки вызовов методов ~ (рекомендуется также перейти к отладке, чтобы увидеть этот метод, если вам интересно)

writeObject напрямую вызывает метод writeObject0(),

public final void writeObject(Object obj) throws IOException {
    ......
    writeObject0(obj, false);
    ......
}

Основная реализация writeObject0 — это разные типы объектов, и для записи сериализованных данных вызываются разные методы.Если объект реализует интерфейс Serializable, вызывается метод writeOrdinaryObject()~

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    ......
   //String类型
    if (obj instanceof String) {
        writeString((String) obj, unshared);
   //数组类型
    } else if (cl.isArray()) {
        writeArray(obj, desc, unshared);
   //枚举类型
    } else if (obj instanceof Enum) {
        writeEnum((Enum<?>) obj, desc, unshared);
   //Serializable实现序列化接口
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);
    } else{
        //其他情况会抛异常~
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }
    ......

writeOrdinaryObject() сначала вызовет writeClassDesc(desc) для записи сгенерированной информации о классе, а затем вызовет метод writeSerialData для записи сериализованных данных.

    private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
            ......
            //调用ObjectStreamClass的写入方法
            writeClassDesc(desc, false);
            // 判断是否实现了Externalizable接口
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //写入序列化数据
                writeSerialData(obj, desc);
            }
            .....
    }

writeSerialData() реализует запись данных поля сериализованного объекта.

  private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        for (int i = 0; i < slots.length; i++) {
            if (slotDesc.hasWriteObjectMethod()) {
                   //如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
                    slotDesc.invokeWriteObject(obj, this);
            } else {
                // 调用默认的方法写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }

Метод defaultWriteFields() получает базовые данные типа данных класса и записывает их непосредственно в базовый байтовый контейнер; получает данные класса obj, рекурсивно вызывает метод writeObject0() для записи данных~

   private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {   
        // 获取类的基本数据类型数据,保存到primVals字节数组
        desc.getPrimFieldValues(obj, primVals);
        //primVals的基本类型数据写到底层字节容器
        bout.write(primVals, 0, primDataSize, false);

        // 获取对应类的所有字段对象
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        // 获取类的obj类型数据,保存到objVals字节数组
        desc.getObjFieldValues(obj, objVals);
        //对所有Object类型的字段,循环
        for (int i = 0; i < objVals.length; i++) {
            ......
              //递归调用writeObject0()方法,写入对应的数据
            writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            ......
        }
    }

Семь, некоторые моменты внимания для сериализации в ежедневной разработке

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

Статические статические переменные и временные измененные поля не сериализуются

Статические статические переменные и переходные измененные поля не будут сериализованы. Давайте рассмотрим пример для анализа волны~ Класс Student добавляет переменную класса и специальность переходного модифицированного поля.

public class Student implements Serializable {

    private Integer age;
    private String name;

    public static String gender = "男";
    transient  String specialty = "计算机专业";

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    @Override
    public String toString() {
        return "Student{" +"age=" + age + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", specialty='" + specialty + '\'' +
                '}';
    }
    ......

Распечатайте объект студента, сериализуйте его в файл, затем измените значение статической переменной, десериализуйте его и выведите десериализованный объект~

результат операции:

序列化前Student{age=25, name='jayWei', gender='男', specialty='计算机专业'}
序列化后Student{age=25, name='jayWei', gender='女', specialty='null'}

Результаты сравнения показывают, что:

  • 1) Пол статической переменной до сериализации явно "мужской", после сериализации модифицируется в программе, после десериализации становится "женским".what? Очевидно, что это статическое свойство не сериализуется. фактически,Статические (статические) переменные-члены относятся к уровню класса, а сериализация предназначена для объектов ~ поэтому ее нельзя сериализовать..
  • 2) После процесса сериализации и десериализации значение переменной специального поля становится пустым из «основного компьютера», почему? Фактически, из-за переходного ключевого слова,Предотвращает сериализацию декорированных полей в файл., после десериализации значение поля transient устанавливается в начальное значение, например, значение типа int будет установлено в 0, а начальное значение типа объекта будет установлено в null.

проблема с serialVersionUID

поверхность serialVersionUID означаетИдентификатор серийного номера версии, На самом деле, каждый класс, реализующий интерфейс Serializable, имеет статическую переменную, представляющую сериализованный идентификатор версии, либо равный 1L по умолчанию, либо равный хэш-коду объекта.

private static final long serialVersionUID = -6384871967268653799L;

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

Механизм сериализации JAVA заключается в проверке согласованности версии путем оценки serialVersionUID класса. Во время десериализации JVM сравнивает serialVersionUID во входящем потоке байтов с serialVersionUID соответствующего локального класса сущностей. Если они совпадают, десериализация выполнена успешно. Если они не совпадают, создается исключение InvalidClassException.

Далее, давайте проверим это, изменим класс Student, а затем десериализуем его.

Exception in thread "main" java.io.InvalidClassException: com.example.demo.Student;
local class incompatible: stream classdesc serialVersionUID = 3096644667492403394,
local class serialVersionUID = 4429793331949928814
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.example.demo.Test.main(Test.java:20)

Как видно из информации об исключении стека журнала, класс в файловом потоке отличается от класса в текущем пути к классам, и их serialVersionUID отличаются, поэтому десериализация вызывает исключение InvalidClassException. Итак, что, если вам действительно нужно изменить класс Student и успешно его десериализовать? Вы можете вручную указать значение serialVersionUID, которое обычно может быть установлено на 1L или, или позволить нашей IDE редактора сгенерировать

private static final long serialVersionUID = -6564022808907262054L;

На самом деле, в руководстве по разработке Ali обязательно модифицировать поле serialVersionUID при добавлении новых свойств в класс сериализации~

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

Добавьте переменную-член типа «Учитель» в класс «Ученик», где «Учитель» не реализует интерфейс сериализации.

public class Student implements Serializable {
    
    private Integer age;
    private String name;
    private Teacher teacher;
    ...
}
//Teacher 没有实现
public class Teacher  {
......
}

Выполняется сериализация, и сообщается об исключении NotSerializableException.

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:16)

На самом деле ответ можно найти в базовом анализе исходного кода в предыдущем разделе.Процесс сериализации объекта будет циклически вызывать его поле типа объекта и рекурсивно вызывать сериализацию.То есть, при сериализации класса Student, Учитель будет обработан класс Serialization, но интерфейс сериализации не реализован для Teacher, поэтому выбрасывается NotSerializableException. Поэтому, если переменная-член созданного класса является типом объекта, класс типа объекта должен реализовывать сериализацию.

Подкласс реализует Serializable.Если родительский класс не реализует интерфейс Serializable, родительский класс не будет сериализован.

Подкласс Student реализует интерфейс Serializable, а родительский класс User не реализует интерфейс Serializable.

//父类实现了Serializable接口
public class Student  extends User implements Serializable {

    private Integer age;
    private String name;
}
//父类没有实现Serializable接口
public class User {
    String userId;
}

Student student = new Student();
student.setAge(25);
student.setName("jayWei");
student.setUserId("1");

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();

//反序列化结果
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student1 = (Student) objectInputStream.readObject();
System.out.println(student1.getUserId());
//output
/** 
 * null
 */

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

Восемь, сериализация общих вопросов интервью

  • Как реализован нижний уровень сериализации?
  • При сериализации, как заставить некоторые элементы не сериализоваться?
  • В чем разница между Serializable и Externalizable в Java
  • Какая польза от serialVersionUID?
  • Можно ли настроить процесс сериализации или переопределить процесс сериализации по умолчанию в Java?
  • Какие переменные во время сериализации Java не сериализуются?

1. Как сериализация базового?

Шестой раздел этой статьи может ответить на этот вопрос, например, ответить на роль ключевого слова Serializable, флага сериализации и его роли в исходном коде.Также он может ответить на несколько основных методов writeObject, таких как непосредственное написание базового тип и получение данных типа obj, писать рекурсивно, ха-ха~

2. Как при сериализации некоторые члены могут не сериализоваться?

Его можно изменить с помощью ключевого слова transient, что может предотвратить сериализацию измененного поля в файл.После десериализации значение переходного поля устанавливается в начальное значение.Например, будет установлено значение типа int на 0, и значение типа объекта будет установлено на 0. Начальное значение будет установлено на ноль.

3. В чем разница между Serializable и Externalizable в Java

Externalizable наследует Serializable и предоставляет нам методы writeExternal() и readExternal(), позволяющие нам управлять механизмом сериализации Java, не полагаясь на сериализацию Java по умолчанию. Правильная реализация интерфейса Externalizable может значительно повысить производительность вашего приложения.

4. Какая польза от serialVersionUID?

可以看回本文第七小节哈,JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。 Во время десериализации JVM сравнивает serialVersionUID во входящем потоке байтов с serialVersionUID соответствующего локального класса сущностей. Если они совпадают, десериализация выполнена успешно. Если они не совпадают, создается исключение InvalidClassException.

5. Можно ли настроить процесс сериализации или можно переопределить процесс сериализации по умолчанию в Java?

В ПОРЯДКЕ. Все мы знаем, что для сериализации объекта вам нужно вызвать ObjectOutputStream.writeObject(saveThisObject) и использовать ObjectInputStream.readObject() для чтения объекта, но виртуальная машина Java предоставляет вам еще одну вещь, а именно определение этих двух методы. Если эти два метода определены в классе, JVM будет вызывать оба метода вместо применения механизма сериализации по умолчанию. В то же время эти методы можно объявить закрытыми, чтобы избежать наследования, переопределения или перегрузки.

6. Какие переменные не сериализуются при сериализации Java?

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

Ссылка и спасибо

Личный публичный аккаунт

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