zoukankan      html  css  js  c++  java
  • 笔记: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)

       

  • 相关阅读:
    python json 和 pickle的补充 hashlib configparser logging
    go 流程语句 if goto for swich
    go array slice map make new操作
    go 基础
    块级元素 行内元素 空元素
    咽炎就医用药(慢性肥厚性咽炎)
    春季感冒是风寒还是风热(转的文章)
    秋季感冒 咳嗽 怎么选药
    解决IE浏览器“无法显示此网页”的问题
    常用的 css 样式 记录
  • 原文地址:https://www.cnblogs.com/li3807/p/6810356.html
Copyright © 2011-2022 走看看