java сериализация, достаточно прочитать это

Java

1. Смысл, значение и сценарии использования сериализацииВо-вторых, способ сериализации1. Сериализуемый1.1 Обычная сериализация1.2 Элементы сериализации ссылок1.3 Механизм многократной сериализации одного и того же объекта1.4 Возможные проблемы алгоритма сериализации Java1.5 Необязательная пользовательская сериализация2. Внешний: принудительная пользовательская сериализация3. Сравнение двух сериализаций3. Серийный номер версии serialVersionUID4. Резюме

1. Смысл, значение и сценарии использования сериализации

  • Сериализация: запись объекта в поток ввода-вывода
  • Десериализация: восстановление объектов из потока ввода-вывода
  • Значение: механизм сериализации позволяет преобразовать сериализацию объектов Java в последовательности байтов, которые можно сохранить на диске или передать по сети для последующего восстановления исходного объекта. Механизм сериализации позволяет объектам существовать независимо от выполнения программы.
  • Сценарий использования: Все объекты, которые могут передаваться по сети, должны быть сериализуемыми, т.е.Например, RMI (remote method invoke, то есть удаленный вызов метода), входящие параметры или возвращаемые объекты сериализуемы, иначе произойдет ошибка;Все объекты Java, которые необходимо сохранить на диск, должны быть сериализуемыми. Обычно рекомендуется, чтобы каждый класс JavaBean, создаваемый программой, реализовывал интерфейс Serializeable.

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

Если объект нужно сохранить на диск или передать по сети, то этот класс должен реализоватьSerializableинтерфейс илиExternalizableодин из интерфейсов.

1. Сериализуемый

1.1 Обычная сериализация

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

  1. Этапы сериализации:
  • Шаг 1: Создайте выходной поток ObjectOutputStream;

  • Шаг 2: Вызовите writeObject объекта ObjectOutputStream для вывода сериализуемого объекта.

    public class Person implements Serializable {
      private String name;
      private int age;
      //我不提供无参构造器
      public Person(String name, int age) {
          this.name = name;
          this.age = age;
      }

      @Override
      public String toString() {
          return "Person{" +
                  "name='" + name + '\'' +
                  ", age=" + age +
                  '}';
      }
    }

    public class WriteObject {
      public static void main(String[] args) {
          try (//创建一个ObjectOutputStream输出流
               ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"))) {
              //将对象序列化到文件s
              Person person = new Person("9龙", 23);
              oos.writeObject(person);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }
  1. Этапы десериализации:
  • Шаг 1: Создайте входной поток ObjectInputStream;

  • Шаг 2: Вызовите readObject() объекта ObjectInputStream, чтобы получить сериализованный объект.

    Мы десериализуем обратно объект человека, сериализованный в файл person.txt выше.

    public class Person implements Serializable {
      private String name;
      private int age;
      //我不提供无参构造器
      public Person(String name, int age) {
          System.out.println("反序列化,你调用我了吗?");
          this.name = name;
          this.age = age;
      }

      @Override
      public String toString() {
          return "Person{" +
                  "name='" + name + '\'' +
                  ", age=" + age +
                  '}';
      }
    }

    public class ReadObject {
      public static void main(String[] args) {
          try (//创建一个ObjectInputStream输入流
               ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) {
              Person brady = (Person) ois.readObject();
              System.out.println(brady);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
    }
    //输出结果
    //Person{name='9龙', age=23}

    Вывод waht???? говорит нам, что десериализация не вызывает конструктор. Десериализованный объект — это объект, созданный самой JVM, а не конструктором.

1.2 Элементы сериализации ссылок

Если член сериализуемого класса не является типом-примитивом или типом String, то ссылочный тип также должен быть сериализуемым; в противном случае класс не может быть сериализован.

Глядя на пример, мы добавляем новый класс учителя. Удалите Person из реализации кода Serializable интерфейса.

public class Person{
    //省略相关属性与方法
}
public class Teacher implements Serializable {

    private String name;
    private Person person;

    public Teacher(String name, Person person) {
        this.name = name;
        this.person = person;
    }

     public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("路飞", 20);
            Teacher teacher = new Teacher("雷利", person);
            oos.writeObject(teacher);
        }
    }
}

Мы видим, что программа сообщает об ошибке напрямую, потому что объект класса Person не сериализуем, что приводит к несериализуемому объекту Учитель

1.3 Механизм многократной сериализации одного и того же объекта

Если один и тот же объект сериализуется несколько раз, будет ли этот объект сериализован несколько раз?ответотрицательныйиз.

public class WriteTeacher {
    public static void main(String[] args) throws Exception {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
            Person person = new Person("路飞", 20);
            Teacher t1 = new Teacher("雷利", person);
            Teacher t2 = new Teacher("红发香克斯", person);
            //依次将4个对象写入输入流
            oos.writeObject(t1);
            oos.writeObject(t2);
            oos.writeObject(person);
            oos.writeObject(t2);
        }
    }
}

Поочередно сериализуйте объекты t1, t2, person и t2 в файл Teacher.txt.

Примечание. Порядок десериализации такой же, как и порядок сериализации..

public class ReadTeacher {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
            Teacher t1 = (Teacher) ois.readObject();
            Teacher t2 = (Teacher) ois.readObject();
            Person p = (Person) ois.readObject();
            Teacher t3 = (Teacher) ois.readObject();
            System.out.println(t1 == t2);
            System.out.println(t1.getPerson() == p);
            System.out.println(t2.getPerson() == p);
            System.out.println(t2 == t3);
            System.out.println(t1.getPerson() == t2.getPerson());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//输出结果
//false
//true
//true
//true
//true

По результатам вывода видно, чтоJava сериализует один и тот же объект и не сериализует этот объект несколько раз, чтобы получить несколько объектов.

  • Алгоритм сериализации Java
  1. Все объекты, сохраняемые на диск, имеют серийный номер.

  2. Когда программа пытается сериализовать объект, она сначала проверяет, был ли этот объект сериализован, и только если объект никогда не был сериализован (в этой виртуальной машине), объект будет сериализован как вывод последовательности байтов.

  3. Если этот объект был сериализован, просто выведите номер напрямую.

    Проиллюстрирован описанный выше процесс сериализации.

1.4 Возможные проблемы алгоритма сериализации Java

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

public class WriteObject {
    public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
             ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
            //第一次序列化person
            Person person = new Person("9龙", 23);
            oos.writeObject(person);
            System.out.println(person);

            //修改name
            person.setName("海贼王");
            System.out.println(person);
            //第二次序列化person
            oos.writeObject(person);

            //依次反序列化出p1、p2
            Person p1 = (Person) ios.readObject();
            Person p2 = (Person) ios.readObject();
            System.out.println(p1 == p2);
            System.out.println(p1.getName().equals(p2.getName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//输出结果
//Person{name='9龙', age=23}
//Person{name='海贼王', age=23}
//true
//true
1.5 Необязательная пользовательская сериализация
  1. Иногда у нас есть такое требование, что некоторые свойства не нужно сериализовать.Используйте ключевое слово transient для выбора полей, которые не нуждаются в сериализации.

    public class Person implements Serializable {
       //不需要序列化名字与年龄
       private transient String name;
       private transient int age;
       private int height;
       private transient boolean singlehood;
       public Person(String name, int age) {
           this.name = name;
           this.age = age;
       }
       //省略get,set方法
    }

    public class TransientTest {
       public static void main(String[] args) throws Exception {
           try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
               Person person = new Person("9龙", 23);
               person.setHeight(185);
               System.out.println(person);
               oos.writeObject(person);
               Person p1 = (Person)ios.readObject();
               System.out.println(p1);
           }
       }
    }
    //输出结果
    //Person{name='9龙', age=23', singlehood=true', height=185cm}
    //Person{name='null', age=0', singlehood=false', height=185cm}

    Из вывода мы видим,При использовании переходных измененных свойств это поле будет игнорироваться при сериализации java, поэтому десериализованный объект, переходные измененные свойства являются значениями по умолчанию. Для ссылочных типов значение равно null, для примитивных типов значение равно 0, для логических типов значение равно false.

  2. Использование переходного процесса, хотя и простое, полностью изолирует это свойство от сериализации. Java предоставляетНеобязательная пользовательская сериализация.Можно управлять методом сериализации или кодировать и шифровать сериализованные данные.

    private void writeObject(java.io.ObjectOutputStream out) throws IOException;
    private void readObject(java.io.ObjectIutputStream in) throws IOException,ClassNotFoundException;
    private void readObjectNoData() throws ObjectStreamException;

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

    public class Person implements Serializable {
       private String name;
       private int age;
       //省略构造方法,get及set方法

       private void writeObject(ObjectOutputStream out) throws IOException {
           //将名字反转写入二进制流
           out.writeObject(new StringBuffer(this.name).reverse());
           out.writeInt(age);
       }

       private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{
           //将读出的字符串反转恢复回来
           this.name = ((StringBuffer)ins.readObject()).reverse().toString();
           this.age = ins.readInt();
       }
    }

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

  3. Более тщательная пользовательская сериализация

    ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
    ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

    • writeReplace: во время сериализации сначала вызывается этот метод, а затем вызывается метод writeObject. Этот метод заменяет целевой сериализованный объект произвольным объектом.

      public class Person implements Serializable {
        private String name;
        private int age;
        //省略构造方法,get及set方法

        private Object writeReplace() throws ObjectStreamException {
            ArrayList<Object> list = new ArrayList<>(2);
            list.add(this.name);
            list.add(this.age);
            return list;
        }

         public static void main(String[] args) throws Exception {
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                 ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
                Person person = new Person("9龙", 23);
                oos.writeObject(person);
                ArrayList list = (ArrayList)ios.readObject();
                System.out.println(list);
            }
        }
      }
      //输出结果
      //[9龙, 23]
    • readResolve: заменить десериализованный объект во время десериализации, и десериализованный объект немедленно отбрасывается. Этот метод вызывается после readeObject.

      public class Person implements Serializable {
          private String name;
          private int age;
          //省略构造方法,get及set方法
           private Object readResolve() throws ObjectStreamException{
              return new ("brady", 23);
          }
          public static void main(String[] args) throws Exception {
              try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
                   ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"))) {
                  Person person = new Person("9龙", 23);
                  oos.writeObject(person);
                  HashMap map = (HashMap)ios.readObject();
                  System.out.println(map);
              }
          }
      }
      //输出结果
      //{brady=23}

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

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

2. Внешний: принудительная пользовательская сериализация

При реализации интерфейса Externalizable должны быть реализованы методы writeExternal и readExternal.

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

    private String name;
    private int age;
    //注意,必须加上pulic 无参构造器
    public ExPerson() {
    }

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

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        //将name反转后写入二进制流
        StringBuffer reverse = new StringBuffer(name).reverse();
        System.out.println(reverse.toString());
        out.writeObject(reverse);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //将读取的字符串反转后赋值给name实例变量
        this.name = ((StringBuffer) in.readObject()).reverse().toString();
        System.out.println(name);
        this.age = in.readInt();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"))) {
            oos.writeObject(new ExPerson("brady", 23));
            ExPerson ep = (ExPerson) ois.readObject();
            System.out.println(ep);
        }
    }
}
//输出结果
//ydarb
//brady
//ExPerson{name='brady', age=23}

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

3. Сравнение двух сериализаций

Реализовать сериализуемый интерфейс Реализовать интерфейс Externalizable
Система автоматически сохраняет необходимую информацию Программисты решают, какую информацию хранить
Встроенная поддержка Java, простая в реализации, нужно только реализовать этот интерфейс, поддержка кода не требуется Два метода внутри интерфейса должны быть реализованы
немного хуже производительность немного лучшая производительность

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

3. Серийный номер версии serialVersionUID

мы знаем,Десериализация должна иметь файл класса, но при обновлении проекта файл класса также будет обновлен.Как сериализация может обеспечить совместимость до и после обновления?

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

public class Person implements Serializable {
    //序列化版本号
    private static final long serialVersionUID = 1111013L;
    private String name;
    private int age;
    //省略构造方法及get,set
}

Если десериализация используетномер версии классаиспользуется при сериализациинепоследовательный, десериализация будетСообщается об исключении InvalidClassException.

Сериализованный номер версии может быть указан произвольно, если он не указан, JVM сама рассчитает номер версии в соответствии с информацией о классе, так что при обновлении класса его нельзя будет корректно десериализовать; еще одна очевидная скрытая опасность не указание номера версии - это не способствует JVM Для миграции между классами файлы классов можно не менять, но разные jvms могут иметь разные правила расчета, что также приведет к невозможности десериализации.

При каких обстоятельствах вам нужно изменить serialVersionUID? Есть три случая.

  • Если изменяется только метод и десериализация не может быть затронута, нет необходимости изменять номер версии;
  • Если изменяются только статические переменные и переходные переменные (переходные измененные переменные), десериализация не затрагивается, и нет необходимости изменять номер версии;
  • Десериализация может завершиться ошибкой, если будут изменены непереходные переменные.Если тип переменной экземпляра в новом классе не соответствует типу класса во время сериализации, десериализация завершится ошибкой, и в это время необходимо изменить serialVersionUID.Если добавляется только переменная экземпляра, новое значение, добавленное после десериализации, является значением по умолчанию; если переменная экземпляра уменьшена, уменьшенная переменная экземпляра будет игнорироваться во время десериализации.

4. Резюме

  1. Все объекты, которым требуется передача по сети, должны реализовать интерфейс сериализации, рекомендуя, чтобы все компоненты javaBeans реализовывали интерфейс Serializable.
  2. Имена классов объектов, переменные экземпляра (включая примитивные типы, массивы и ссылки на другие объекты) будут сериализованы; методы, переменные класса и временные переменные экземпляра не будут сериализованы.
  3. Если вы хотите, чтобы переменная не сериализовалась, используйте модификатор transient.
  4. Переменная-член ссылочного типа сериализованного объекта также должна быть сериализуемой, иначе будет сообщено об ошибке.
  5. При десериализации должен быть файл класса для сериализованного объекта.
  6. При чтении сериализованного объекта через файл или сеть он должен считываться в том порядке, в котором он был фактически записан.
  7. Для сериализации одноэлементного класса необходимо переписать метод readResolve(), иначе будет нарушен принцип одноэлементности.
  8. Один и тот же объект сериализуется несколько раз, только первая сериализация является бинарным потоком, а номер сериализации сохраняется только в будущем, и сериализация повторяться не будет.
  9. Рекомендуется добавить номер версии serialVersionUID ко всем сериализуемым классам, чтобы облегчить обновление проекта.