六对象的序列化和反序列化
(1)序列化和反序列化概述
Java提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的
数据、有关对象的类型的信息和存储在对象中数据的类型。
将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对
象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上
反序列化该对象。
(2)序列化与基本类型序列化
1)序列化
将类型int 转换成4byte或将其他数据类型转换成byte的过程叫序列化
数据---->n byte
2)反序列化
将n个byte 转换成一个数据的过程,也就是序列化的反过程。
nbyte ---> 数据
3)RandomAccessFile提供基本类型的读写方法,可以将基本类型数据序列化到文件或者将文件内容反序列化为数
据。
(3)ObjectInputStream类和ObjectOutputStream类
ObjectInputStream类和ObjectOutputStream类是高层次的数据流,它们包含序列化和反序列化对象的方法。
ObjectInputStream类的部分方法:
ObjectOutputStream类的方法:
ObjectOutputStream类包含很多写方法来写各种数据类型,但是一个特别的方法writeObject(Object o)例外。该方
法序列化一个对象,并将它发送到输出流。
相似的ObjectInputStream类包含如下反序列化一个对象的方法readObject() 。该方法从流中取出下一个对象,并
将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。
为了演示序列化在Java中是怎样工作的,我们使用Student类,假设我们定义了如下的Student类,该类实现了
Serializable接口。Student.java源文件代码:
import java.io.*; public class Student implements Serializable{ private String stuno; private String stuname; private int stuage; public Student(String stuno, String stuname, int stuage) { super(); this.stuno = stuno; this.stuname = stuname; this.stuage = stuage; } public String getStuno() { return stuno; } public void setStuno(String stuno) { this.stuno = stuno; } public String getStuname() { return stuname; } public void setStuname(String stuname) { this.stuname = stuname; } public int getStuage() { return stuage; } public void setStuage(int stuage) { this.stuage = stuage; } @Override public String toString() { return "Student [stuno=" + stuno + ", stuname=" + stuname + ", stuage="+ stuage + "]"; } }
请注意,一个类的对象要想序列化成功,必须满足两个条件:
1)该类必须实现Serializable接口。
2)该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
如果你想知道一个Java标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简
单, 只需要查看该类有没有实现Serializable接口。
(4)序列化对象和反序列化对象
对象必须实现序列化接口 ,才能进行序列化,否则将出现异常。这个接口,没有任何方法,只是一个标准。
ObjectOutputStream类用来序列化一个对象,如下的SerializeDemo例子实例化了一个Student对象,并将该对象
序列化到一个文件中。该程序执行后,就创建了一个名为student.ser文件。该程序没有任何输出,但是你可以通过代
码研读来理解程序的作用。
注意: 当序列化一个对象到文件时, 按照Java的标准约定是给文件一个.ser扩展名。
示例1:
import java.io.*; public class ObjectSeriaDemo1 { public static void main(String[] args) throws Exception{ String file = "E:\\Java\\JavaSE\\IO\\demo\\obj.dat"; //1对象的序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); Student stu = new Student("10001", "张三", 20); oos.writeObject(stu); oos.flush(); oos.close(); //2对象的反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Student stu1 = (Student)ois.readObject(); System.out.println(stu1); ois.close(); } }
运行结果:
这里要注意以下要点:
readObject()方法中的try/catch代码块尝试捕获 ClassNotFoundException异常。对于JVM可以反序列化对象,它
必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个ClassNotFoundException异
常。
readObject()方法的返回值被转化成Student引用。
(5)transient关键字
方法声明:
private void writeObject(java.io.ObjectOutputStream s) throws IOException private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
经过改写的Student.java源代码;
import java.io.*; public class Student implements Serializable{ private String stuno; private String stuname; //该元素不会进行jvm默认的序列化,也可以自己完成这个元素的序列化 private transient int stuage; public Student(String stuno, String stuname, int stuage) { super(); this.stuno = stuno; this.stuname = stuname; this.stuage = stuage; } public String getStuno() { return stuno; } public void setStuno(String stuno) { this.stuno = stuno; } public String getStuname() { return stuname; } public void setStuname(String stuname) { this.stuname = stuname; } public int getStuage() { return stuage; } public void setStuage(int stuage) { this.stuage = stuage; } @Override public String toString() { return "Student [stuno=" + stuno + ", stuname=" + stuname + ", stuage="+ stuage + "]"; } private void writeObject(ObjectOutputStream s) throws IOException{ s.defaultWriteObject();//把jvm能默认序列化的元素进行序列化操作 s.writeInt(stuage);//自己完成stuage的序列化 } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException{ s.defaultReadObject();//把jvm能默认反序列化的元素进行反序列化操作 this.stuage = s.readInt();//自己完成stuage的反序列化操作 } }
再次运行结果:和上述的结果一样
关于的两种序列化的性能分析可以看ArrayList源码中序列化和反序列化的问题。我不是很清楚,就不解释了。
(6)序列化中子类和父类构造函数的调用问题
在进行对象序列化和反序列化的时候存在构造函数的是否调用的问题,我们来看下面的实例。
实例:
import java.io.*; public class ObjectSeriaDemo2 { public static void main(String[] args) throws Exception{ //序列化 ObjectOutputStream oos=new ObjectOutputStream( new FileOutputStream("E:\\Java\\JavaSE\\IO\\demo\\obj1.dat")); Foo2 foo2 = new Foo2(); oos.writeObject(foo2); oos.flush(); oos.close(); System.out.println(); //反序列化是否递归调用父类的构造函数 ObjectInputStream ois = newObjectInputStream( new FileInputStream("E:\\Java\\JavaSE\\IO\\demo\\obj1.dat")); Foo2 foo21 = (Foo2)ois.readObject(); System.out.println(foo21); ois.close(); System.out.println("-------------------"); //序列化 ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("E:\\Java\\JavaSE\\IO\\demo\\obj1.dat")); Bar2 bar2 = new Bar2(); oos1.writeObject(bar2); oos1.flush(); oos1.close(); //反序列化 System.out.println(); ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("E:\\Java\\JavaSE\\IO\\demo\\obj1.dat")); Bar2 bar21 = (Bar2)ois1.readObject(); System.out.println(bar21); ois1.close(); /* * 对子类对象进行反序列化操作时, * 如果其父类没有实现序列化接口 * 那么其父类的构造函数会被调用 */ } } /* *一个类实现了序列化接口,那么其子类都可以进行序列化 */ class Foo implements Serializable{ public Foo(){ System.out.println("foo..."); } } class Foo1 extends Foo{ public Foo1(){ System.out.println("foo1..."); } } class Foo2 extends Foo1{ public Foo2(){ System.out.println("foo2..."); } } class Bar{ public Bar(){ System.out.println("bar"); } } class Bar1 extends Bar{ public Bar1(){ System.out.println("bar1.."); } } class Bar2 extends Bar1 implements Serializable{ public Bar2(){ System.out.println("bar2..."); } }
运行结果: