Сериализация/десериализация, я вас долго терпел

Java
Сериализация/десериализация, я вас долго терпел

image

Эта статья Проект с открытым исходным кодом Github:GitHub.com/Hanson.net 9…Он был включен в , есть подробные маршруты обучения программированию для самообучения, вопросы интервью и личные встречи, материалы по программированию и серия технических статей и т. д. Ресурсы постоянно обновляются...


инструмент человек

Когда-то понимание сериализации Java застряло в: «реализацияSerializbaleинтерфейс" не годится, пока...

Так что на этот раз я нашел время, чтобы снова собрать давние «Идеи программирования на Java», точно так же, как раньше«Перечисление частей знания»Точно так же я перепроверил знания о «сериализации и десериализации».


Для чего используется сериализация?

Первоначальная цель сериализации состоит в том, чтобы «преобразовать» объект Java в последовательность байтов, чтобы упростить постоянное хранение на диске, избежать исчезновения объекта из памяти после запуска программы и преобразовать его в байты. Последовательности также легче транспортировать. и распространяться по сети, поэтому концептуально хорошо понятны:

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

image

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

Дело в том, что это выглядит очень просто, но позади еще много вещей, пожалуйста, посмотрите вниз.


Как сериализуются объекты?

Однако в настоящее время в Java нет ключевого слова для прямого определения так называемого «постоянного» объекта.

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

Например, если бы мыStudentОбъект класса сериализуется в файл с именемstudent.txtв текстовом файле, а затем десериализовать текстовый файл вStudentобъект класса:

image

1. Определение студенческого класса

public class Student implements Serializable {

    private String name;
    private Integer age;
    private Integer score;
    
    @Override
    public String toString() {
        return "Student:" + '\n' +
        "name = " + this.name + '\n' +
        "age = " + this.age + '\n' +
        "score = " + this.score + '\n'
        ;
    }
    
    // ... 其他省略 ...
}

2. Сериализация

public static void serialize(  ) throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 1000 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
    
    System.out.println("序列化成功!已经生成student.txt文件");
    System.out.println("==============================================");
}

3. Десериализация

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
}

4. Запуск результатов

Отпечатки консоли:

序列化成功!已经生成student.txt文件
==============================================
反序列化结果为:
Student:
name = CodeSheep
age = 18
score = 1000

Каково использование интерфейса Serializable?

определено вышеStudentкласс, который реализуетSerializableинтерфейс, однако, когда мы нажимаем наSerializableЗагляните внутрь интерфейса и найдите его.Оказался пустой интерфейс, и не содержит никаких методов!

image

Представьте, если вышеизложенное определено вStudentЗабыл добавитьimplements Serializableчто случится?

Результат эксперимента: программа работает в это времясообщит об ошибке, и бросаетNotSerializableExceptionаномальный:

image

Следим за сообщением об ошибке, от исходного кода кObjectOutputStreamизwriteObject0()На дне метода я вдруг понял:

image

если объект не является нинить,множество,перечислить, а также не реализовалSerializableЕсли интерфейс используется, он будет выброшен во время сериализации.NotSerializableExceptionаномальный!

О, я вижу!

оригинальныйSerializableИнтерфейс просто маркер! ! !

Он сообщает код, пока он реализованSerializableВсе классы интерфейса сериализуемы! Однако ему не нужно выполнять реальное действие сериализации.


serialVersionUIDКакая польза от номера?

Я считаю, что вы должны часто видеть следующие строки кода, определенные в некоторых классах, которые определяют класс с именемserialVersionUIDполя:

private static final long serialVersionUID = -4392658638228508589L;

Вы знаете, что означает это утверждение? Зачем делатьserialVersionUIDсерийный номер?

Продолжайте делать простой эксперимент и возьмите вышеперечисленное.Studentнапример, мы не объявляли явноserialVersionUIDполе.

Сначала мы вызываем вышеуказанноеserialize()метод, аStudentОбъект сериализован на локальный дискstudent.txtдокумент:

public static void serialize() throws IOException {

    Student student = new Student();
    student.setName("CodeSheep");
    student.setAge( 18 );
    student.setScore( 100 );

    ObjectOutputStream objectOutputStream = 
        new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) );
    objectOutputStream.writeObject( student );
    objectOutputStream.close();
}

Далее мыStudentПеремещайте руки и ноги в классе, например, добавьте другое имя с именемstudentIDполе, указывающее номер студенческого билета:

image

В это время мы берем только что сериализованные данные в локальныйstudent.txtфайл и десериализуйте его с помощью следующего кода, пытаясь восстановить только чтоStudentОбъект:

public static void deserialize(  ) throws IOException, ClassNotFoundException {
    ObjectInputStream objectInputStream = 
        new ObjectInputStream( new FileInputStream( new File("student.txt") ) );
    Student student = (Student) objectInputStream.readObject();
    objectInputStream.close();
    
    System.out.println("反序列化结果为:");
    System.out.println( student );
}

запустить обнаружениесообщил об ошибке, и бросаетInvalidClassExceptionаномальный:

image

Информация, предложенная здесь, очень ясна: до и после сериализации.serialVersionUIDНомера не совпадают!

По крайней мере, с этого местадваВажная информация:

  • 1. serialVersionUID — уникальный идентификатор до и после сериализации
  • 2. По умолчанию, если никто явно не определилserialVersionUID, компилятор автоматически объявит его для него!

Вопрос 1: serialVersionUIDИдентификатор сериализации можно рассматривать как "секрет" в процессе сериализации и десериализации. Во время десериализации JVM сравнивает идентификатор серийного номера в потоке байтов с идентификатором серийного номера в сериализованном классе. Да, только когда они оба последовательно может быть выполнена десериализация снова, в противном случае будет сообщено об исключении, чтобы завершить процесс десериализации.

2-й вопрос:Если при определении сериализуемого класса никто явно не определяетserialVersionUIDЕсли это так, среда выполнения Java автоматически создастserialVersionUID, после изменения структуры или информации класса, как указано выше, классserialVersionUIDТоже изменится!

Таким образом, дляserialVersionUIDОпределенность кода, по-прежнему рекомендуется при написании кода, любойimplements Serializableкласс, лучше явно объявитьserialVersionUIDЧеткое значение!

Конечно, если вы не хотите назначать значения вручную, вы также можете использовать функцию автоматического добавления IDE, такую ​​как я используюIntelliJ IDEA,в соответствии сalt + enterможет быть автоматически сгенерирован и добавлен для классаserialVersionUIDполе, очень удобно:

image


два особых случая

  • 1. Все, что естьstaticИзмененные поля не сериализуются
  • 2. Все, что естьtransientПоля, измененные модификаторами, также не сериализуются.

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

для второй точки, ты должен знатьtransientМодификаторы работают.

Если вы не хотите, чтобы поле сериализовалось при сериализации объекта класса (например, это поле хранит частные значения, такие как:密码д.), то вы можете использоватьtransientмодификатор для украшения поля.

такие, как ранее определенныеStudentкласс, добавьполе пароля, но не хотите сериализоваться вtxtтекст, вы можете:

image

Это сериализацияStudentобъект класса,passwordДля полей установлены значения по умолчаниюnull, что видно из результатов, полученных от десериализации:

image


Контролируемая и принудительная сериализация

Связующее благословение

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

В конце концов, десериализация также эквивалентна«Неявное» построение объекта, поэтому мы хотим сделатьконтролируемыйДействие десериализации объекта.

Какой контролируемый закон?

Ответ:Напишите свой собственныйreadObject()Функция десериализации построения объектов, что обеспечивает ограничения.

Поскольку вы сами пишетеreadObject()функция, то вы можете делать много контролируемых вещей: например, различные суждения.

также вышеStudentНапример, в классе обычно оценки учащихся должны быть в0 ~ 100Между тем, чтобы предотвратить подделку тестовых результатов студентов странным значением во время десериализации, мы можем написать свой собственныйreadObject()Функция контроля десериализации:

private void readObject( ObjectInputStream objectInputStream ) throws IOException, ClassNotFoundException {

    // 调用默认的反序列化函数
    objectInputStream.defaultReadObject();

    // 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
    if( 0 > score || 100 < score ) {
        throw new IllegalArgumentException("学生分数只能在0到100之间!");
    }
}

Например, я намеренно изменил оценку учащегося на101, десериализация немедленно прекращается и сообщается об ошибке:

image

Для приведенного выше кода некоторым друзьям может быть любопытно, почему пользовательскийprivateизreadObject()Метод может быть вызван автоматически, что требует от вас следовать базовому исходному коду, чтобы узнать, я помогу вам следоватьObjectStreamClassНижний слой класса, я думаю, вы вдруг поймете, когда увидите это:

image

Механизм отражения снова работает! Да, в Java все можно "отражать" (забавно), даже если это определено в классеprivateЧастные методы также могут быть извлечены и выполнены, что просто удобно.

Усовершенствования одноэлементного шаблона

Легко упускаемый из виду вопрос:Сериализуемые одноэлементные классы не могут быть одноэлементными!

Небольшой пример кода прояснит ситуацию.

Например, здесь мы используемjavaНапишите одноэлементную реализацию общего подхода «статический внутренний класс»:

public class Singleton implements Serializable {

    private static final long serialVersionUID = -1576643344804979563L;

    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton singleton = new Singleton();
    }

    public static synchronized Singleton getSingleton() {
        return SingletonHolder.singleton;
    }
}

Затем напишите основную функцию проверки:

public class Test2 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(
                    new FileOutputStream( new File("singleton.txt") )
                );
        // 将单例对象先序列化到文本文件singleton.txt中
        objectOutputStream.writeObject( Singleton.getSingleton() );
        objectOutputStream.close();

        ObjectInputStream objectInputStream =
                new ObjectInputStream(
                    new FileInputStream( new File("singleton.txt") )
                );
        // 将文本文件singleton.txt中的对象反序列化为singleton1
        Singleton singleton1 = (Singleton) objectInputStream.readObject();
        objectInputStream.close();

        Singleton singleton2 = Singleton.getSingleton();

        // 运行结果竟打印 false !
        System.out.println( singleton1 == singleton2 );
    }

}

После запуска мы обнаружили:Десериализованный одноэлементный объект не равен исходному одноэлементному объекту., что, несомненно, не соответствует нашей цели.

Решение: написано от руки в классе singletonreadResolve()функция, которая напрямую возвращает одноэлементный объект, чтобы избежать его:

private Object readResolve() {
    return SingletonHolder.singleton;
}

image

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


не ожидал

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

Вот и все, увидимся в следующий раз.

Эта статья Проект с открытым исходным кодом Github:GitHub.com/Hanson.net 9…Он был включен в , есть подробные маршруты обучения программированию для самообучения, вопросы интервью и личные встречи, материалы по программированию и серия технических статей и т. д. Ресурсы постоянно обновляются...


Медленнее, быстрее