Java I/O (2) - 对象的输入输出与序列化

先说概念:

一、相关概念

序列化:把内存中的java对象转换成与平台无关的二进制字节序列,以便永久保存在磁盘上或通过网络进行传输。序列化是Java提供的一种将对象写入到输出流、并在之后将其读回的机制。

Java提供的对对象进行读写的流对象(即承载对象的媒介)为ObjectOutputStream 和 ObjectInputStream ,它们的作用就是把对象转换为字节序列,并承载这些序列,并提供了writeObject() 和 readObject() 方法对这些字节序列进行读写。

那么,如何实现对象的序列化与反序列化呢?

二、序列化、反序列化的方法

方法(一)-- Serializable接口

2.1 使用Serializable接口的默认序列化机制

1、实现Serializable接口

如果某个类对象要被序列化,该类必须实现Serializable接口,只接口内无具体方法,所以只需要声明实现该接口,而无需实现具体方法。

public class Student implements Serializable { 
    …… }

2、通过ObjectOutputStream 类对象的writeObject(obj)方法,将obj参数指定的对象进行序列化,把得到的字节序列写到目标输出流中。

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("student.dat"));
        Student s = new Student();
        out.writeObject(s);
        out.close();

3、通过ObjInputStream类对象的readObject()方法从源输入流中读取字节序列,再反序列化为对象,并将其返回。

        //反序列化得到对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("student.dat"));
        Student s1 = (Student) in.readObject();
        in.close();

 

以上是简单的对象序列化,使用默认序列化机制,会将对象的全部域都进行序列化,同时对该对象引用的其他对象也进行序列化,同样的,对这些引用对象进行序列化的时候如果有另外的引用,这些引用也会被序列化,以此类推。如果一个对象包含的成员变量是容器对象,容器的元素还是容器,那么序列化过程会变得十分繁琐。另外,有时候对象的一些数据域只在本地有意义,对这样的数据进行序列化后存储或传输没有意义,甚至有时会在反序列化后使用对象时引起崩溃。所以,我们需要指定一些字段不需要序列化。

2.2 自定义序列化

序列化的实际应用中通常需要忽略某些数据域或简化序列化过程,此时就需要自定义序列化的过程,自定义序列化的方法有如下几种:1、使用transient关键字;2、使用writeObject()和readObject()方法  3、使用External接口实现序列化。 

1、使用transient关键字

在对未来会进行序列化的类进行定义时,使用transient关键字声明无需序列化的字段。默认序列化机制会忽略被transient修饰的字段。

2、使用writeObject()与readObject()

在可序列化的类中添加私有的writeObject和readObject方法,定义这个两个方法后,不再使用默认的序列化机制,而是执行这两个方法中的内容。在这个方法中可以向默认的读写行为中添加验证或任何其他行为。

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        in.defaultReadObject();//调用默认序列化机制
        //添加逻辑
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();//调用默认序列化机制
        //添加逻辑  
    }

使用这种方法可以序列化某些不可被序列化的域。有类似这样的情形,某可序列化的类中一个域是某不可序列化的类的对象,但是又需要反序列化的时候获取这个对象。此时可以使用这里提到的方法进行序列化。

step1:将不可序列化的类的对象声明为transient,以避免NotSerializableException。示例:

(student可序列化,teacher不可)

public class Student implements Serializable {

    private String name;
    private int age;
    private transient Teacher teacher;
                  …………
}

step2:writeObject方法中调用defaultWriteObject方法写出可序列化的域,然后使用标准的DataOutput调用写出不可序列化的域中的数据。defaultWriteObject是ObjectOutputStream类的一个特殊方法,只能在可序列化类的writeObject方法中调用。注意该方法为private类型。示例:

    private void writeObject(ObjectOutputStream out) throws IOException {
        //对可序列化的域进行序列化
        out.defaultWriteObject();
        //不可序列化的域对该域内基本数据类型分别进行序列化
        out.writeInt(teacher.getCourseNo());
        out.writeUTF(teacher.getName());
    }

step3:readObject方法中反过来执行上述过程注意该方法也为private类型。示例:

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        //对可序列化的域进行反序列化
        in.defaultReadObject();
        //不可序列化的域通过对该域内基本数据类型分别进行反序列化,然后重新创建该域
        int courseNo = in.readInt();
        String name = in.readUTF();
        teacher = new Teacher(courseNo, name);
    }

完整示例代码:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Student implements Serializable {

    private String name;
    private int age;
    private transient Teacher teacher;
    public Student(){}

    public void setName(String n, int a){
        this.name = n;
        this.age = a;
    }
    public String getName(){
        return this.name;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
        //对可序列化的域进行反序列化
        in.defaultReadObject();
        //不可序列化的域通过对该域内基本数据类型分别进行反序列化,然后重新创建该域
        int courseNo = in.readInt();
        String name = in.readUTF();
        teacher = new Teacher(courseNo, name);
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        //对可序列化的域进行序列化
        out.defaultWriteObject();
        //不可序列化的域对该域内基本数据类型分别进行序列化
        out.writeInt(teacher.getCourseNo());
        out.writeUTF(teacher.getName());
    }
}
Student.java
public class Teacher {
    private int courseNo;
    private String name;

    public int getCourseNo() {
        return courseNo;
    }

    public String getName() {
        return name;
    }

    public Teacher(int courseNo, String name){
        this.courseNo = courseNo;
        this.name = name;
    }

}
Teacher.java

注意,

方法(二)-- External接口

3、使用Externalizable接口

无论是使用transient关键字,还是使用writeObject()和readObject()方法,其实都是基于Serializable接口的序列化。除了让序列化机制来保存和恢复对象,类还可以定义它自己的机制。JDK中提供了另一个序列化接口--Externalizable,使用该接口之后,之前基于Serializable接口的序列化机制就将失效。声明实现Externalizable接口的类必须重写readExternal()和writeExternal()方法。示例:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Student2 implements Externalizable {
    public String name;
    public int age;
    public Teacher teacher;
    public Student2(){}

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
        out.writeInt(age);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
        age = in.readInt();

    }
}

Externalizable继承于Serializable,跟readObject方法不同的是,使用该接口时,序列化的细节需要程序员自行完成,writeExternal和readExternal方法需要对包括超类数据在内的整个对象的存储和恢复负全责。

在写出对象时,序列化机制在输出流中仅仅是记录该对象所属的类。在读入可外部化的类时,对象输入流会调用类的无参构造函数创建一个对象,然后调用readExternal方法。所以实现Externalizable的类必须提供一个无参构造函数,并且权限为public

注意:readObject和writeObject是私有的,并且只能被序列化机制调用;readExternal 和 writeExternal是公共的,readExternal还潜在的允许修改现有对象的状态。

三、总结

1、java序列化就是为了存储或传输对象的某个瞬时状态。

2、java中实现对象序列化的方式有两种:

方法一:

  可序列化类实现Serializable接口,该接口默认情况下没有需要实现的方法。

  如果有不可序列化的字段,如果该字段在不需要反序列化,则使用transient关键字声明该字段即可;如果该字段在反序列化后还有需要,则通过在可序列化类中定义writeObject和readObjcet方法,将不可序列化的字段中的数据分别进行序列化后,在反序列化时读取数据并重新创建对象。

方法二:

  可序列化类实现Extrernal接口,该接口必须实现readExternal和weiteExternal方法。并在该方法内对每个需要进行序列化的字段挨个进行序列化,反序列化时再挨个反序列化恢复。

 

posted on 2019-04-27 19:08  at_today  阅读(399)  评论(0编辑  收藏  举报

导航