对象序列化和反序列化

对象序列化和反序列化

在网络通信中,任何数据都是以二进制的形式来传输的。对象序列化可以把内存中的Java对象转成二进制流,而二进制流可以存放在本地磁盘文件中,通过网络或程序来获取该二进制流后,就能将该二进制流恢复成Java对象。序列化的这一过程就是将对象状态信息转换为可存储或传输的过程。

对象序列化的作用:

  • 对象序列化可以把对象以字节序列的形式永久保存在磁盘中。
  • 对象序列化可以用于在网络中传输对象。
  • 对象序列化使得对象可以脱离程序的运行而独立存在。

序列化与反序列化

序列化是将一个Java对象写入IO流中,转换成字节序列,而反序列化是用IO流将字节序列恢复成Java对象。要实现对象序列化,该对象的类必须实现接口Serializable,该接口没任何方法,仅作标记,说明该类可以实现序列化。

若序列化对象的成员变量是一个引用类型,则该引用类型变量的类也要实现Serializable接口,保证序列化过程中用到的类都要实现序列化,不然会出现异常。

对象序列化步骤:

  1. 使用ObjectOuputStream,使用writeObject()将一个对象序列化。也就是将对象转成二进制流,存放在磁盘文件中,实现持久化。
public class Person implements Serializable{
    private String name;
    private int age;
    private String sex;    
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }/**省略setter 和 getter*/
}

//把对象写入序列化文件,完成对象序列化
public class Test {
    public static void main(String[] args) throws Exception {
    FileOutputStream fos = new FileOutputStream("D:\Object01.txt");//存放对象字节序列的文件路径
    //对象输出流中的节点流是一个缓冲流,该流包装节点流,所以需要flush()
    ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos)); 
    Person p = new Person("张三", 15, "");
    oos.writeObject(p);
    oos.flush();    
    }    
} 

对象反序列化步骤

调用ObjectInputStream的readObject()方法读取序列化文件中的二进制流,转成对象

FileInputStream fis = new FileInputStream("D:\Object01.txt");
        //创建一个ObjectInputStream输入流,节点流数据源是一个对象序列化文件
        ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
        //读取Person对象
        Person p = (Person)ois.readObject();
        System.out.println(p.getName() + "||" + p.getAge() +"||" + p.getSex());

使用反序列化的注意点

  • 反序列化将二进制流数据转成对象,所以在此之前要保证该类存在,不然Java对象没有存在意义。
  • 如果读取的序列化文件中存在多个对象,则必须按照他们序列化的顺序来读取,不然会出错。
  • 如果成员变量使用transient修饰,那么反序列化后,该成员变量数据没有。
  • 反序列化的对象没有使用构造器来初始化。
 

测试序列化多个对象:

public class Person implements Serializable{
    private String name;
    private int age;
    private transient String sex; //透明化,表示该成员变量不会序列化
    
    public Person(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    /** 省略getter 和 setter */
}
public class Test {
    public static void main(String[] args) throws Exception {
        //存放对象字节序列的文件路径
        FileOutputStream fos = new FileOutputStream("D:\MoreObject.txt");
        //对节点流使用了缓冲流,所以需要flush()
        ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos)); 
        Person p = new Person("张三", 15, "");
        Person p1 = new Person("李四", 20, "");
        Person p2 = new Person("王五", 25, "");
        //序列化多个对象
        oos.writeObject(p);
        oos.writeObject(p1);
        oos.writeObject(p2);
        oos.flush();
        
        //创建一个对象输入流ObjectInputStream
        FileInputStream fis = new FileInputStream("D:\MoreObject.txt");
        ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
        //恢复多个对象
        Person per1 = (Person) ois.readObject();
        Person per2 = (Person) ois.readObject();
        Person per3 = (Person) ois.readObject();
//        Person per4 = (Person) ois.readObject(); //EOFException
        
        System.out.println(per1.getName() + "||" + per1.getAge() +"||" + per1.getSex());
        System.out.println(per2.getName() + "||" + per2.getAge() +"||" + per2.getSex());
        System.out.println(per3.getName() + "||" + per3.getAge() +"||" + per3.getSex());
        
//        输出结果:
//        张三||15||null
//        李四||20||null
//        王五||25||null
    }
}
  • 实例说明:
  1. 被transient修饰的成员变量sex,无法对数据序列化,所以输出结果中是null,因为该值是引用类型,如果是值类型就是系统默认的初始值。
  2. 恢复的对象顺序是和序列化对象一致的,这是因为反序列化文件有多个对象,恢复的顺序也是按照对象序列化时的顺序来恢复的。
  3. 如果序列化文件中的对象已经读取完了,再次readObject()会引发EOFException异常。