• 笔记:I/O流-对象序列化


    Java 语言支持一种称为对象序列化(Object Serialization)的非常通用的机制,可以将任何对象写入到流中,并在之后将其读回,首先需要支持对象序列化的类,必须继承与 Serializable 接口,该接口没有任何方法,只是对类起到标记的作用,然后使用 ObjectOutputStream 流来序列化对象,使用 ObjectInputStream 流来反序列化,示例代码如下:

    • 对象类声明:

      public class Employee implements Serializable {

              private String name;

              private String sex;

              public Employee() {

              }

              public Employee(String name, String sex) {

                      this.name = name;

                      this.sex = sex;

              }

              // getter 和 setter 方法

      }

         

      public class Manager extends Employee {

              private Employee secretary;

              public Manager(){

              }

              public Manager(String name, String sex) {

                      super(name, sex);

              }

              // getter 和 setter 方法

      }

    • 创建对象实例:

       Employee harry = new Employee("Harry Hacker", "男");

       Manager boss = new Manager("Carl Cracker", "女");

      boss.setSecretary(harry);

    • 序列化到文件

        ObjectOutputStream outputStream = null;

               try {

                      outputStream = new ObjectOutputStream(new FileOutputStream("serializableApp.dat"));

                      outputStream.writeObject(boss);

               } catch (FileNotFoundException ex) {

                      ex.printStackTrace();

                } finally {

                      if (outputStream != null) {

                             outputStream.close();

                      }

               }

    • 从文件反序列化

                    ObjectInputStream inputStream = null;

                    try {

                            inputStream = new ObjectInputStream(new FileInputStream("serializableApp.dat"));

                            Manager serializableBoss = (Manager) inputStream.readObject();

                            System.out.println("manager name is " + serializableBoss.getName() + " sex is "

                                            + serializableBoss.getSex() + " secretary is "

                                            + serializableBoss.getSecretary().getName());

                    } catch (ClassNotFoundException ex) {

                            ex.printStackTrace();

                    } catch (FileNotFoundException ex) {

                            ex.printStackTrace();

                    } finally {

                            if (inputStream != null) {

                                    inputStream.close();

                            }

                    }

    每个对象都用一个序列号保存的,对象序列化机制如下:

    • 对于遇到的每一个对象引用都关联一个序列号
    • 对于每一个对象,当第一次遇到时,保存器对象数据到流中
    • 如果某个对象已经被保存过,那么只写出保存的序列号
    • 对于流中的对象,在第一次遇到其序列号时,创建他,并使用流中数据来初始化他,然后记录这个顺序号和新对象之间的关联
    • 当遇到对象引用另一个对象的序列号时,获取与这个序列号相关联的对象引用

    某些数据域时不可以序列化的,例如,只对本地方法有意义的存储文件句柄或窗口句柄的整数值等,Java 拥有一种简单的机制来防止这种域被序列化,那就是将他们标记成 transient,如果被标记为不可序列化的类,也需要将其标记为 transient,瞬时域在对象序列化时总是被跳过,示例如下:

        private transient Point2D.Double point;

       

    1. 修改默认的序列化机制

      序列化机制单个的类提供了一种方式,去向默认的读写行为添加验证或任何其他想要的行为,可序列化类可以定义具体有如下签名的方法:

              private void readObject(ObjectInputStream in)

                              throws IOException,ClassNotFoundException;

                

              private void writeObject(ObjectOutputStream out)

                              throws IOException;

      readObject writeObject 方法只需要保存和加载本类的数据域,而不需要关注基类(超类)数据和任何其他类的信息,实现给方法的具体示例如下:

           private void readObject(ObjectInputStream in)

                              throws IOException, ClassNotFoundException {

                      // 读取序列化字段数据

                      in.defaultReadObject();

                      // 其他数据校验或者序列化

              }

              private void writeObject(ObjectOutputStream out)

                              throws IOException{

                      // 写入序列化字段数据

                      out.defaultWriteObject();

                      // 其他数据校验或者序列化

             }

      除了可以使用readObject writeObject方法来保存和恢复对象数据外,类还可以定义他自己的机制,类需要实现 Externalizable 接口,该接口定义了两个方法:

          public void readExternal(ObjectInput in)

               throws IOException, ClassNotFoundException;

         

          public void writeExternal(ObjectOutput out)

                  throws IOException ;

      这些方法对包括超类数据在内的整个对象的存储和恢复负全责,而序列化机制在流中仅仅只是记录该对象所属的类,示例代码如下:

    • 基类代码:

              public void writeExternal(ObjectOutput out) throws IOException {

                      out.writeUTF(this.name);

                      out.writeUTF(this.sex);

              }

              public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

                      this.name = in.readUTF();

                      this.sex = in.readUTF();

              }

    • 子类代码:

      @Override

      public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

              super.readExternal(in);

              this.secretary = new Employee();

              this.secretary.readExternal(in);

       }

      @Override

      public void writeExternal(ObjectOutput out) throws IOException {

              super.writeExternal(out);

             this.secretary.writeExternal(out);

       }

         

    1. 序列化单例和类型安全的枚举

      在序列化和反序列化时,如果目标对象时唯一的,使用默认的序列化机制时不适用的,因为默认的序列化机制,即使构造器时私有的,序列化机制也可以创建新的对象,因此在进行==(比较)时将失败,为了解决整个问题需要定义一个名称为 readResolve的特殊方法,在对象被序列化之后就会调用他,返回一个对象,而该对象之后会称为 readObject 的返回值,示例代码如下:

      public class Orientation implements Serializable {

              public static final Orientation HORIZONTAL = new Orientation(1);

              public static final Orientation VERTICAL = new Orientation(2);

         

              private int value;

         

              private Orientation(int value) {

                      this.value = value;

              }

         

              protected Object readResolve() throws ObjectStreamException {

                      if (value == 1) {

                              return HORIZONTAL;

                      }

                      if (value == 2) {

                              return VERTICAL;

                      }

                        

                      return null;

              }

      }

    2. 版本管理

      无论类的定义产生了什么样的变化,他的SHA指纹也会跟着变化,而我们知道对象流拒绝读入具有不同指纹的对象,但是,类可以表明他对其早期版本保持兼容,在类的所有较新的版本都必须把 serialVersionUID 常量定义与最初版本的指纹相同,如果一个类具有名为 serialVersionUID 的静态数据成员,就不需要在人工的计算其指纹,而只需直接使用整个值,示例如下:

      public class Employee implements Serializable, Externalizable {

              public static final long serialVersionUID = -2349238498234324L;

      }

      如果类只有方法产生了变化,那么在读入新对象数据时是不会有任何问题的,如果是数据域产生了变化,那么就可能会有问题,常见情况如下:

    • 如果数据域之间名字匹配而类型不匹配,那么对象流不会进行类型转换,因此不兼容
    • 如果流中的对象具有当前版本中所没有的数据域,那么对象流会忽视这些额外的数据
    • 如果当前版本具有在流化对象中所没有的数据域,那么这些新增加的域将被设置成他们的默认值(对象是null、数字为 0,布尔类型为 false)

       

  • 相关阅读:
    WPF DataGrid ListView等控件Binding LINQ数据源
    WPF自定义命令
    vb.net与FLASH的完美结合
    [音乐欣赏]鲍家街43号 汪峰 小鸟
    MSGRID的填充
    听!是谁在唱歌
    学习用的几个英文单词
    [学习日记]三层结构
    有关从文件完整路径中提取文件名的方法
    有关TABCONTROL选项卡的动态选择方法
  • 原文地址:https://www.cnblogs.com/li3807/p/6810356.html
Copyright © 2020-2023  润新知