一、为什么要进行序列化
再介绍之前,我们有必要先了解下对象的生命周期,我们知道Java中的对象都是存在于堆内存中的,而堆内存是可以被垃圾回收器不定期回收的。从对象被创建到被回收这一段时间就是Java对象的生命周期,也即Java对象只存活于这个时间段内。
对象被垃圾回收器回收意味着对象和对象中的成员变量所占的内存也就被回收,这意味着我们就再也得不到该对象的任何内容了,因为已经被销毁了嘛,当然我们可以再重新创建,但这时的对象的各种属性都又被重新初始化了。
所以如果我们需要保存对象的状态,然后再在未来的某段时间将该对象再恢复出来的话,则必须要在对象被销毁即被垃圾回收器回收之前保存对象的状态。要保存对象状态的话,我们可以使用文件、数据库,也可以使用序列化。
这里我们主要介绍对象序列化。我们很有必要了解这方面的内容,因为对象序列化不仅在保存对象状态时可以被用到(对象持久化),在Java中的远程方法调用也会被用到,在网络中要传输对象的话,则必须要对对象进行序列化。
简单总结起来,进行对象序列化的话的主要原因就是实现 对象持久化 和 进行网络传输(zhaohao PS:是因为要先从内存中拿出对象,通过网络传输),这里先只介绍怎样通过对象序列化保存对象的状态。
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以【二进制序列】的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的【对象】传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
二、怎样进行对象序列化
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并可以在以后将这个字节序列完全恢复为原来的对象。
这一过程甚至可以通过网络进行(这对于实现远程调用是非常重要的)。这同样意味着序列化机制能弥补不同操作系统间的差异。
也就是说,可以在运行Windows系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里准确的重新组装。
为了序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,这时,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。
要将一个序列重组为一个对象,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject(),并将对象进行向下转型。
假设我们要保存Person类的某三个对象的name、age、height这三个成员变量,当然这里只是简单举例
我们先看下Person类,要序列化某个类的对象的话,则该类必要实现Serializable接口,从JavaAPI中我们发现该接口是个空接口,即该接口中没声明任何方法。
-
importjava.io.Serializable;
-
publicclassPersonimplementsSerializable{
-
intage;
-
intheight;
-
Stringname;
-
publicPerson(Stringname,intage,intheight){
-
this.name=name;
-
this.age=age;
-
this.height=height;
-
}
-
}
下面我们看一下如何来进行序列化,这其中主要涉及到Java的I/O方面的内容,主要用到两个类FileOutputStream和ObjectOutputStream,
下面是具体代码:
-
importjava.io.FileOutputStream;
-
importjava.io.IOException;
-
importjava.io.ObjectOutputStream;
-
publicclassMyTestSer{
-
-
-
-
publicstaticvoidmain(String[]args){
-
Personzhangsan=newPerson("zhangsan",30,170);
-
Personlisi=newPerson("lisi",35,175);
-
Personwangwu=newPerson("wangwu",28,178);
-
try{
-
-
ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream("person.ser"));
-
out.writeObject(zhangsan);
-
out.writeObject(lisi);
-
out.writeObject(wangwu);
-
out.close();
-
}catch(IOExceptione){
-
e.printStackTrace();
-
}
-
}
-
}
三、对象的反序列化
我们存储的目的主要是为了再恢复使用,下面我们来看下加上反序列化后的代码:
-
importjava.io.FileInputStream;
-
importjava.io.FileOutputStream;
-
importjava.io.IOException;
-
importjava.io.ObjectInputStream;
-
importjava.io.ObjectOutputStream;
-
publicclassMyTestSer{
-
-
-
-
publicstaticvoidmain(String[]args){
-
Personzhangsan=newPerson("zhangsan",30,170);
-
Personlisi=newPerson("lisi",35,175);
-
Personwangwu=newPerson("wangwu",28,178);
-
try{
-
-
ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream("person.ser"));
-
out.writeObject(zhangsan);
-
out.writeObject(lisi);
-
out.writeObject(wangwu);
-
}catch(IOExceptione){
-
e.printStackTrace();
-
}
-
try{
-
ObjectInputStreamin=newObjectInputStream(newFileInputStream("person.ser"));
-
Personone=(Person)in.readObject();
-
Persontwo=(Person)in.readObject();
-
Personthree=(Person)in.readObject();
-
System.out.println("name:"+one.name+"age:"+one.age+"height:"+one.height);
-
System.out.println("name:"+two.name+"age:"+two.age+"height:"+two.height);
-
System.out.println("name:"+three.name+"age:"+three.age+"height:"+three.height);
-
}catch(Exceptione){
-
e.printStackTrace();
-
}
-
}
-
}
输出结果如下:
-
name:zhangsanage:30height:170
-
name:zhangsanage:35height:175
-
name:zhangsanage:28height:178
从添加的代码我们可以看到进行反序列化也很简单,主要用到的流是FileInputstream和ObjectInputstream正好与存储时用到的流相对应。另外从结果顺序我们可以看到反序列化后得到对象的顺序与序列化时的顺序一致。
四、总结
进行对象序列化主要目的是为了保存对象的状态(成员变量)。
进行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用于连接磁盘文件,并把字节写出到该磁盘文件;ObjectOutputStream主要用于将对象写出为可转化为字节的数据。
要将某类的对象序列化,则该类必须实现Serializable接口,该接口仅是一个标志,告诉JVM该类的对象可以被序列化。
如果某类未实现Serializable接口,则该类对象不能实现序列化。
保存状态的目的就是为了在未来的某个时候再恢复保存的内容,这可以通过反序列化来实现。对象的反序列化过程与序列化正好相反,主要用到的两个流是FileInputstream和ObjectInputStream。
反序列化后得到的对象的顺序与保存时的顺序一致。
五、补充
补充一:上面我们举得例子很简单,要保存的成员变量要么是基本类型的要么是String类型的。但有时成员变量有可能是引用类型的,这是的情况会复杂一点。那就是当要对某对象进行序列化时,该对象中的引用变量所引用的对象也会被同时序列化,并且该对象中如果也有引用变量的话则该对象也将被序列化。总结说来就是在序列化的时候,对象中的所有引用变量所对应的对象将会被同时序列化。这意味着,引用变量类型也都要实现Serializable接口。当然其他对象的序列化都是自动进行的。所以我们只要保证里面的引用类型是都实现Serializable接口就行了,如果没有的话,会在编译时抛出异常。如果序列化的对象中包含没有实现Serializable的成员变量的话,这时可以使用transient关键字,让序列化的时候跳过该成员变量。使用关键字transient可以让你在序列化的时候自动跳过transient所修饰的成员变量,在反序列化时这些变量会恢复到默认值。
补充二:如果某类实现了Serializable接口的话,其子类会自动编程可序列化的,这个好理解,继承嘛。
补充三:在反序列化的时候,并不会调用对象的构造器,这也好理解,如果调用了构造器的话,对象的状态不就又重新初始化了吗。
补充四:我们说到对象序列化的是为了保存对象的状态,即对象的成员变量,所以静态变量不会被序列化。