JavaSE第九十六讲:对象的序列化与反序列化深入讲解

来源:互联网 发布:电子签名软件 编辑:程序博客网 时间:2024/04/29 16:34

1. 在上一讲的学习内容中,我们有学到字符的编码方式,现在我们继续写一个程序:获得当前系统所需要的字符集

如何获得当前系统的字符集呢?

查看JDK Doc中的CharSet类,这个类是位于java.nio.charset包中这是在新的io包中,具体这块内容留在以后再去讲解。

获得当前系统的字符集,查看里面的方法public static SortedMap<String,Charset> availableCharsets() 返回一个带排序的Map

程序Demo:

package com.ahuier.io2;import java.util.Iterator;import java.util.Set;import java.util.SortedMap;import java.nio.charset.Charset;public class CharSetTest3 {public static void main(String[] args) {SortedMap<String,Charset> map = Charset.availableCharsets();//两种遍历map的方式Set<String> set = map.keySet();for(Iterator<String> iter = set.iterator(); iter.hasNext();){System.out.println(iter.next());}}}

编译执行结果:就不贴出来了。

2. 序列化

1) 将对象转换为字节流[注意是字节流,不是字符流]保存起来,并在以后还原这个对象,这种机制叫做对象序列化。
2) 将一个对象保存到永久存储设备上称为持久化。
3) 一个对象要想能够实现序列化,必须实现Serializable接口或Externalizable接口。[我们通常继承第一个接口,因为第二个接口它也继承了第一个接口]

[比如说:在内存中new出一个person对象,我们把这个对象保存的文件中去,因为内存里面的东西一旦虚拟机关闭,数据就会丢失,如果保存到文件中,等下一个虚拟机再启动的时候直接读取文件person数据进来,这就是实现一个序列化的功能]

查看JDK文档中 Serializable接口,发现里面什么都没有,这很像之前学习的Annotation接口,就是mark Annotation标示性的接口,这里也是mark interface 标示性的接口

一个类若想被序列化,则需要实现 java.io.Serializable 接口,该接口中没有定义任何方法,是一个标识性接口(Marker Interface),当一个类实现了该接口,就表示这个 类的对象是可以序列化的。


4) 序列化(serialization)是把一个对象的状态写入一个字节流的过程。当你想要把你的程序状态存到一个固定的存储区域例如文件时,它是很管用的。稍后一点时间,你就可以运用序列化过程存储这些对象[与之对应是反序列化,它是从文件加载到内存中的去,正好与序列化相反]

5) 假设一个被序列化的对象引用了其他对象,同样,其他对象又引用了更多的对象。这一系列的对象和它们的关系形成了一个顺序图表。在这个对象图表中也有循环引用。也就是说,对象X可以含有一个对象Y的引用,对象Y同样可以包含一个对象X的引用。对象同样可以包含它们自己的引用。对象序列化和反序列化的工具被设计出来并在这一假定条件下运行良好。如果你试图序列化一个对象图表中顶层的对象,所有的其他的引用对象都被循环的定位和序列化。同样,在反序列化过程中,所有的这些对象以及它们的引用都被正确的恢复


6)当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。[因为静态变量是属于类的本身,它不属于对象,所以静态不会被存储到文件当中]

7)如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存。

8)如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化

9) Serializable接口

(1) 只有一个实现Serializable接口的对象可以被序列化工具存储和恢复。Serializable接口没有定义任何成员。它只用来表示一个类可以被序列化。如果一个类可以序列化,它的所有子类都可以序列化。
(2) 声明成transient的变量不被序列化工具存储。同样,static变量也不被存储

总结一下:

    在序列化时,static 变量是无法序列化的;如果 A 包含了对 B 的引用,那么在序列化 A 的时候也会将 B 一并地序列化;如果此时 A 可以序列化,B 无法序列化,那么当 序列化 A 的时候就会发生异常,这时就需要将对 B 的引用设为 transient,该关键字 表示变量不会被序列化。 

10) ObjectOutput接口 [序列化中比较重要的接口]

   ObjectOutput 继承DataOutput接口并且支持对象序列化。特别注意writeObject()方法,它被称为序列化一个对象。所有这些方法在出错情况下引发IOException 异常

查看JDK Doc文档中ObjectOutput接口与writeObject()方法

void writeObject(Object obj) throws IOException
    Write an object to the underlying storage or stream. The class that implements this interface defines how the object is written.

[将底层的对象写到底层的存储或者流当中,实现了这个接口的类定义了对象是如何去写的,这个流可以是FileOutputStream,这样就可以写到文件当中去了,obj就是将要被写的对象]

11) ObjectOutputStream类[对象输出流类]

ObjectOutputStream类继承OutputStream 类和实现ObjectOutput 接口。它负责向流写入对象。

该类的构造方法如下:
ObjectOutputStream(OutputStream outStream) throws IOException
参数outStream 是序列化的对象将要写入的输出流

[显然ObjectOutputStream是一个过滤流,如果是节点流它会跟文件,字符串数组这些目标文件打交道,而它接受参数的类型是OutputStream,它会包装那些节点流或者过滤流]

12) ObjectInput [反序列化]
    ObjectInput 接口继承DataInput接口。它支持对象序列化。特别注意 readObject()方法,它叫反序列化对象。所有这些方法在出错情况下引发IOException 异常

查看这个方法 :Object readObject()

疑问:如何知道我们把什么类型的对象存进去呢?所以这边调用的时候要注意向下类型转换

13) ObjectInputStream

ObjectInputStream 继承InputStream 类并实现ObjectInput 接口。ObjectInputStream 负责从流中读取对象。该类的构造方法如下:
ObjectInputStream(InputStream inStream) throws IOException,StreamCorruptedException
参数inStream 是序列化对象将被读取的输入流。

查看JDK Doc文档中的ObjectInputStream类的方法 readObject():
public final Object readObject()throws IOException,ClassNotFoundException
    Read an object from the ObjectInputStream. The class of the object, the signature of the class, and the values of the non-transient and non-static fields of the class and all of its supertypes are read.

[ObjectInputStream读取一个对象,这个对象所对应的类,类的签名和非transient和非静态的成员变量以及所有的父类型的能被read]这就是反序列化中能读取到的信息。

14) 程序Demo:

package com.ahuier.io2;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.Serializable;public class SerializableTest1 {public static void main(String[] args) throws Exception, IOException {Person2 p1 = new Person2(20, "zhangsan", 4.55);Person2 p2 = new Person2(50, "lisi", 4.67);Person2 p3 = new Person2(10, "wangwu", 17.78);/* * 将对象序列化到文件中 用到FileOutputStream 、 ObjectOutputStream */ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));//将p1序列化到文件中oos.writeObject(p1);oos.writeObject(p2);oos.writeObject(p3);oos.close();}}class Person2 implements Serializable {int age;transient String name; //定义为transient,则这个对象就不会被存进去了double height;public Person2(int age, String name, double height) {this.age = age;this.name = name;this.height = height;}}

编译执行结果在工程目录下生成文件,打开这个这个文件显示如下乱码,显然这不是我们需要的,所以接下我们需要用反序列化的形式将这个文本文件的对象反序列化到内存,加载到内存中就是一个一个的对象了,这样我们就可以使用这些对象。

package com.ahuier.io2;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SerializableTest1 {public static void main(String[] args) throws Exception, IOException {Person2 p1 = new Person2(20, "zhangsan", 4.55);Person2 p2 = new Person2(50, "lisi", 4.67);Person2 p3 = new Person2(10, "wangwu", 17.78);/* * 将对象序列化到文件中 用到FileOutputStream 、 ObjectOutputStream */ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));// 将p1序列化到文件中oos.writeObject(p1);oos.writeObject(p2);oos.writeObject(p3);oos.close();System.out.println("-------------------");FileInputStream fis = new FileInputStream("person.txt");ObjectInputStream ois = new ObjectInputStream(fis);Person2 p = null;for (int i = 0; i < 3; i++) {p = (Person2) ois.readObject();System.out.println(p.age + "," + p.name + "," + p.height);}ois.close();}}class Person2 implements Serializable {int age;transient String name; // 定义为transient,则这个对象就不会被存进去了double height;public Person2(int age, String name, double height) {this.age = age;this.name = name;this.height = height;}}

编译执行结果:

-------------------
20,null,4.55
50,null,4.67
10,null,17.78

【说明】:反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息,在内存中重新构建对象

3. 在序列化和反序列化进程中需要特殊处理的 Serializable 类应该实现以下方法(比较很少碰到这种)

1) private void writeObject(java.io.ObjectOutputStream stream)throws IOException;
2) private void readObject(java.io.ObjectInputStream stream)throws IOException, ClassNotFoundException;
这两个方法不属于任何一个类和任何一个接口,是非常特殊的方法.

查看JDK Doc文档中Serializable类的说明:

Classes that require special handling during the serialization and deserialization process must implement special methods with these exact signatures:

 private void writeObject(java.io.ObjectOutputStream out)     throws IOException private void readObject(java.io.ObjectInputStream in)     throws IOException, ClassNotFoundException; private void readObjectNoData()      throws ObjectStreamException;[需要在序列化或者在反序列化当中特殊处理的类,它必须要实现这些特殊的类的精确声明的方法] 3) 程序Demo
package com.ahuier.io2;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SerializableTest2 {public static void main(String[] args) throws Exception, IOException {Person3 p1 = new Person3(20, "zhangsan", 4.55);Person3 p2 = new Person3(50, "lisi", 4.67);Person3 p3 = new Person3(10, "wangwu", 17.78);/* * 将对象序列化到文件中 用到FileOutputStream 、 ObjectOutputStream */ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));// 将p1序列化到文件中oos.writeObject(p1);oos.writeObject(p2);oos.writeObject(p3);oos.close();System.out.println("-------------------");FileInputStream fis = new FileInputStream("person.txt");ObjectInputStream ois = new ObjectInputStream(fis);Person3 p = null;for (int i = 0; i < 3; i++) {p = (Person3) ois.readObject();System.out.println(p.age + "," + p.name + "," + p.height);}ois.close();}}class Person3 implements Serializable {int age;transient String name; // 定义为transient,则这个对象就不会被存进去了double height;public Person3(int age, String name, double height) {this.age = age;this.name = name;this.height = height;}/* * 当我们提供了这两个方法之后,则序列化或者反序列化就由我们自己来控制了 * 如果不提供这两种方法,则还是有ObjectInputStream和ObjectOutputStream来控制 */private void writeObject(java.io.ObjectOutputStream out) throws IOException {out.writeInt(age);out.writeUTF(name);System.out.println("writeObject"); // 每序列化一个对象,就默认调用一次这种方法}private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException {age = in.readInt();name = in.readUTF();System.out.println("readObject");}}

编译执行结果:
writeObject
writeObject
writeObject
-------------------
readObject
20,zhangsan,0.0
readObject
50,lisi,0.0
readObject
10,wangwu,0.0

【说明】:当我们在一个待序列化/反序列化的类中实现了以上两个 private 方法(方法声明要 与上面的保持完全的一致),那么就允许我们以更加底层、更加细粒度的方式控制序 列化/反序列化的过程。此次为止,我们的Java中I/O学习已经告一段落了,虽然学习的很充实的内容,但是有很多方法,流程用起来还是比较不熟练,希望接下去能多挤出一些时间来回顾总结多敲一些代码。