对象持续
持续(persistence)是指对象记录自己的状态以便将来再生的能力。例如,持续对象可能将其状态保存在一个文件中。该文件可以用于在不同运行环境中恢复这个对象。对象本身并不持续,而是构造对象复制品所需的信息持续。对象系列化时,所有对象数据都写入流,但其方法和类定义不写入流。
为什么引入系列化
传统方式(存储数据到磁盘等)需定义特殊数据格式再编写相应的读写该格式数据的功能模块,这样必须建立文件格式和自己数据格式间的映射,而读写功能要么简单缺少扩展要么很复杂难于创建和维护。JAVA对象的寿命通常随生成该对象的程序终止而终止,对象也可能在运行期间被终止。如内存中的对象是暂时的,即当它们超过参考值(或范围)或系统关闭(如掉电),它们就不存在了,而序列化后的对象只要在磁盘、磁带或别的什么地方存在拷贝,它就一直存在。
对象序列化的目的
l 支持运行在不同虚拟机上不同版本类之间的双向通讯
l 定义允许JAVA类读取用相同类较老版本写入的数据流的机制
l 定义允许JAVA类写用相同类较老版本读取的数据流的机制
l 提供对持久性和RMI的序列化
l 产生压缩流且运行良好以使RMI能序列化
l 辨别写入的是否是本地流
l 保持非版本化类的低负载
系列化
系列化就是指对象通过写出描述自己状态的数值来记录自己的过程,即将对象表示成一系列有序字节,java提供了将对象写入流和从流中恢复对象的方法。系列化的主要任务是写出对象实例变量的数值。如果变量是另一对象的引用,则引用的对象也要系列化。这个过程是递归的,系列化过程可能涉及到一个复杂数结构的系列化,包括原有对象、对象的对象、对象的对象的对象,等等。对象所有权的层次结构成为图表(graph)。
并不是所有类都能系列化,只有实现Serializable或Externalizable接口的对象才能成功地系列化。这两个接口都在java.io包中。可系列化对象可用外部对象系列化,实际是一种输出流,具体对象要写出自己的状态,不能让另一个对象做这个工作。
Java语言的对象序列化支持其两种主要特性。Java 的RMI使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对象序列化来传输参数和返回值;对象的序列化也是Java Beans必需的,使用一个Bean时,它的状态信息通常在设计期间配置好,程序启动后,利用对象序列化将这种状态信息保存下来,以便程序启动以后恢复。
Serializable 接口
Serializable 接口本身不包括任何方法。当一个类声明为实现Serializable时,只是声明参加可系列化协议。对象系列化时,如果对象状态写入流中,则流中应包含恢复对象所需的足够信息。即使恢复的类修改成更新的兼容版本,流中也还要包含恢复所要的足够信息。
符合下列条件的类都可以系列化:
l 类或其上级类之一应实现java.io.serializable接口;
l 类必须具有方法以控制存放的数据和将新数据添加到现存的数据中;
l 类必须具有readObject()方法以读取相应writeObject()方法写入的数据。
如果可系列化类有不想系列化的变量(如口令),则需标上关键字transient,即临时字段不会被defaultWriteObject()方法自动保存,其必须在程序中明确保存和恢复。并且字段是在构件器内部初始化的,不是在定义的时候,这就保证了它们不会在重新装配时被某些自动化机制初始化。许多东西如静态数据、打开文件句柄、套接字连接和线程是不系列化的。
Externalizable接口
Externalizable接口标识可以存放成流但却负责保存自身状态的对象。当一个具体化对象写入流时,这个流只负责存放对象的类名,对象要写入自己的数据。Externalizable接口定义如下:
public interface Externalizable extends Serializable{
public void writeExternal(ObjectOutput out)
throws IOException;
public void readExternal(ObjectInput in)
throws IOException,ClassNotFoundException;
}
具体化类必须遵循这个规范。
对象输出流
可系列化其它对象的对象实现java.io包中的ObjectOutput接口,该接口定义如下:
public interface ObjectOutput extends DataOutput{
public void writeObject(Object obj)
throws IOException;
public void write(int b) throws IOException;
public void write(byte b[])throws IOException;
public void write(byte b[],int off,int len)
throws IOException;
public void flush()throws IOException;
public void close() throws IOException;
}
这个接口的主要方法是writeObject(Object obj),将obj写入流中。Obj的静态和临时数据忽略,所有其它变量都写入。ObjectOutput接口扩展DataOutput接口,DataOutput方法写入支持写入原型数据类型。如,writeDouble()方法写入double类型数据,writeBoolean()写入boolean型数据,这些原型类型写入方法用于写出对象的原型实例变量。
对象要系列化成文件时,第一步是建立与文件交流的输出流:
FileOutputStream fo = new FileOutputStream(“obj.file”);
接着生成对象输出流并将它链接到文件输出流:
ObjectOutputStream so = new ObjectOutputStream(fo);
对象输出流可用writeObject()方法将对象的位信息写入流中,如,下列编码构造Point类的实例并将其系列化:
so.writeObject(new Point(15,20));
so.flush();
反系列化
ObjectInputStream类将系列化的流反系列化。程序使用这个类的方法可以从流和主对象所指的整个对象树恢复系列化的对象,也可从对象输入流读取原型数据类型。
系列化异常
系列化和反系列化对象期间可能抛出6种异常:
InvalidClassException 通常在重系列化流无法确定类型时或返回的类无法在取得对象的系统中表示时抛出此异常。异常也在恢复的类不声明为public时或没有public缺省(无变元)构造器时抛出。
NotSerializableException 通常由具体化对象(负责自身的重系列化)探测到输入流错误时抛出。错误通常由意外不变量值指示,或者表示要系列化的对象不可系列化。
StreamCorruptedException 在存放对象的头或控制数据无效时抛出。
OptionalDataException 流中应包含对象但实际只包含原型数据时抛出。
ClassNotFoundException 流的读取端找不到反系列化对象的类时抛出。
IOException 要读取或写入的对象发生与流有关的错误时抛出。
系列化实例
1. 定制数据格式的系列化
验证怎样用writeObject和readObject方法编码一个定制数据格式。当有大量持久性数据时,数据应该以简洁、精简的格式存放。此例子用一个矩形对称阵列,只对其一半数据序列化,即只写/读一半数据再恢复成完整阵列。
/*CustomDataExample.java */
import java.io.*;
public class CustomDataExample1 implements Serializable{
transient int dimension;
transient int thearray[][];
CustomDataExample1(int dim){
dimension=dim;
thearray=new int[dim][dim];
arrayInit();
}
public static void main(String args[]){
CustomDataExample1 corg=new CustomDataExample1(4);
CustomDataExample1 cnew=null;
try{
FileOutputStream fo=new FileOutputStream("cde.tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(corg);
so.flush();
so.close();
}catch (Exception e){
e.printStackTrace();
System.exit(1);
}
try{
FileInputStream fi=new FileInputStream("cde.tmp");
ObjectInputStream si=new ObjectInputStream(fi);
cnew=(CustomDataExample1)si.readObject();
si.close();
}catch(Exception e){
e.printStackTrace();
System.exit(1);
}
System.out.println();
System.out.println("Printing the original array...");
System.out.println(corg);
System.out.println();
System.out.println("Printing the new array...");
System.out.println();
System.out.println(cnew);
System.out.println();
System.out.println("The original and new arrays should be the same!");
System.out.println();
}
private void writeObject(ObjectOutputStream s) throws IOException{
/*先调用缺省的defaultWriteObject()方法保存非临时变元(若有) */
s.defaultWriteObject();
/* 对临时变元明确调用原型数据writeInt()方法以保存其 */
s.writeInt(dimension);
for(int i=0;i<dimension;i++){
for(int j=0;j<=i;j++){
/* 对临时变元明确调用原型数据writeInt()方法以保存其阵列的一半数据 */
s.writeInt(thearray[i][j]);
}
}
}
private void readObject(ObjectInputStream s)
throws IOException,ClassNotFoundException{
/* 先调用缺省的defaultReadObject()方法恢复非临时数据,再显式调用原型数据readInt()方法恢复临时数据,写入和恢复数据的顺序要求一致 */
s.defaultReadObject();
dimension=s.readInt();
thearray=new int[dimension][dimension];
for(int i=0;i<dimension;i++){
for(int j=0;j<=i;j++){
thearray[i][j]=s.readInt();
}
}
for(int i=0;i<dimension;i++){
for(int j=dimension-1;j>i;j--){
thearray[i][j]=thearray[j][i];
}
}
}
/* 此方法的功能是将阵列作成对角阵 */
void arrayInit(){
int x=0;
for(int i=0;i<dimension;i++){
for(int j=0;j<=i;j++){
thearray[i][j]=x;
thearray[j][i]=x;
x++;
}
}
}
public String toString(){
StringBuffer sb=new StringBuffer();
for(int i=0;i<dimension;i++){
for(int j=0;j<dimension;j++){
sb.append(Integer.toString(thearray[i][j])+" ");
}
sb.append("/n");
}
return(sb.toString());
}
}
结果如下:
Printing the original array...
0 1 3 6
1 2 4 7
3 4 5 8
6 7 8 9
Printing the new array...
0 1 3 6
1 2 4 7
3 4 5 8
6 7 8 9
The original and new arrays should be the same!
2.非序列化超类的序列化
当一个已序列化的子类的超类没有序列化时,子类必须显式存储超类的状态。
/*NonSerialSuperExample.java */
import java.io.*;
public class NonSerialSuperExample{
public static void main(String args[]){
Book bookorg=new Book(100,"How to Serialize",true,"R.R","Serialization",97);
Book booknew=null;
try{
FileOutputStream fo=new FileOutputStream("tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(bookorg);
so.flush();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
try{
FileInputStream fi=new FileInputStream("tmp");
ObjectInputStream si=new ObjectInputStream(fi);
booknew=(Book)si.readObject();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
System.out.println();
System.out.println("Printing original book..");
System.out.println(bookorg);
System.out.println("Printing new book..");
System.out.println(booknew);
System.out.println("Both original and new should be the same");
System.out.println();
}
}
/* Book.java */
import java.io.*;
class Book extends ReadingMaterial implements Serializable{
int numpages;
String name;
boolean ishardcover;
public Book(){super();}
public Book(int pages,String n,boolean hardcover,String author,String subject,int yearwritten){
super(author,subject,yearwritten);
numpages=pages;
name=n;
ishardcover=hardcover;
}
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeObject(author);
out.writeObject(subject);
out.writeInt(yearwritten);
}
private void readObject(ObjectInputStream in)
throws IOException,ClassNotFoundException{
in.defaultReadObject();
author=(String)in.readObject();
subject=(String)in.readObject();
yearwritten=in.readInt();
}
public String toString(){
return("Name:"+name+"/n"+"Author:"+author+"/n"+"Pages:"
+numpages+"/n"+"Subject:"+subject+"/n"+"Year:"+yearwritten+"/n");
}
}
/* ReadingMaterial.java */
import java.io.*;
import java.lang.String;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.lang.ClassNotFoundException;
import java.io.IOException;
import java.io.ObjectOutput;
class ReadingMaterial {
protected String author;
protected String subject;
protected int yearwritten;
public ReadingMaterial(){}
public ReadingMaterial(String auth,String sub,int year){
author=auth;
subject=sub;
yearwritten=year;
}
}
结果如下:
Printing original book..
Name:How to Serialize
Author:R.R
Pages:100
Subject:Serialization
Year:97
Printing new book..
Name:How to Serialize
Author:R.R
Pages:100
Subject:Serialization
Year:97
Both original and new should be the same
3.超类具体化的具体化
当用具体化接口时,一个具体化对象必须运行writeExternal()方法存储对象的状态用readExternal方法读取对象的状态。此例子验证了一个怎样存储和恢复它可具体化超类对象的状态。当一个可具体化对象的超类也具体化,子类要在它自己的writeExternal()和readExternal()方法中调用其超类的writeExternal()和readExternal()方法。
/*Savesuper.java */
import java.io.*;
public class Savesuper{
public static void main(String args[]){
Book1 bookorg=new Book1(100,"How to Serialize",true,"R.R","Serialization",97);
Book1 booknew=null;
try{
FileOutputStream fo=new FileOutputStream("tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(bookorg);
so.flush();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
try{
FileInputStream fi=new FileInputStream("tmp");
ObjectInputStream si=new ObjectInputStream(fi);
booknew=(Book1)si.readObject();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
System.out.println();
System.out.println("Printing original book..");
System.out.println(bookorg);
System.out.println("Printing new book..");
System.out.println(booknew);
System.out.println("Both original and new should be the same");
System.out.println();
}
}
/* Book1.java */
import java.io.*;
class Book1 extends ReadingMaterial1 implements Externalizable{
private int numpages;
private String name;
private boolean ishardcover;
public Book1(){super();}
public Book1(int pages,String n,boolean hardcover,String author,String subject,int yearwritten){
super(author,subject,yearwritten);
numpages=pages;
name=n;
ishardcover=hardcover;
}
public void witeExternal(ObjectOutput out) throws IOException{
super.writeExternal(out);
/*out.writeInt(numpages);
*out.writeObject(name);
out.writeBoolean(ishardcover);*/
}
public void readExternal (Object Inputin) throws IOException, ClassNotFoundException{
super.readExternal(in);
numpages=in.readInt();
name=(String)in.readObject();
ishardcover=in.readBoolean();
}
public String toString(){
return("Name:"+name+"/n"+"Author:"+super.getAuthor()+"/n"+"Pages:"
+numpages+"/n"+"Subject:"+super.getSubject()+"/n"+"Year:"+super.getYearwritten()+"/n");
}
}
/* ReadingMaterial1.java */
import java.io.*;
import java.lang.String;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.lang.ClassNotFoundException;
import java.io.IOException;
import java.io.ObjectOutput;
class ReadingMaterial1 implements Externalizable{
private String author;
private String subject;
private int yearwritten;
public ReadingMaterial1(){}
public ReadingMaterial1(String auth,String sub,int year){
author=auth;
subject=sub;
yearwritten=year;
}
public String getAuthor(){
return author;}
public String getSubject(){
return subject;}
public int getYearwritten(){
return yearwritten;}
public void writeExternal(ObjectOutput out) throws IOException{
out.writeObject(author);
out.writeObject(subject);
out.writeInt(yearwritten);
}
public void readExternal(ObjectInput in) throws
IOException, ClassNotFoundException{
author=(String)in.readObject();
subject=(String)in.readObject();
yearwritten=in.readInt();
}
}
结果如下:
Printing original book..
Name:How to Serialize
Author:R.R
Pages:100
Subject:Serialization
Year:97
Printing new book..
Name:null
Author:R.R
Pages:0
Subject:Serialization
Year:97
Both original and new should be the same
4.非具体化超类的具体化
当用具体化接口时,一个具体化对象必须运行writeExternal()方法存储对象的状态用readExternal()方法读取对象的状态。此例子验证了一个对象怎样存储和恢复它非具体化超类的状态。当一个可具体化对象的超类没有具体化,子类必须用它自己的writeExternal()和readExternal()方法明确存储和恢复其超类的可具体化对象状态。
/* Nonexternsuper1.java */
import java.io.*;
public class Nonexternsuper1{
public static void main(String args[]){
Book2 bookorg=new Book2(100,"How to Serialize",true,"R.R","Serialization",97);
Book2 booknew=null;
try{
FileOutputStream fo=new FileOutputStream("tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(bookorg);
so.flush();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
try{
FileInputStream fi=new FileInputStream("tmp");
ObjectInputStream si=new ObjectInputStream(fi);
booknew=(Book2)si.readObject();
}catch (Exception e){
System.out.println(e);
System.exit(1);
}
System.out.println();
System.out.println("Printing original book..");
System.out.println(bookorg);
System.out.println("Printing new book..");
System.out.println(booknew);
System.out.println("Both original and new should be the same");
System.out.println();
}
}
/* Book2.java */
import java.io.*;
class Book2 extends ReadingMaterial2 implements Externalizable{
int numpages;
String name;
boolean ishardcover;
public Book2(){super();}
Book2(int pages,String n,boolean hardcover,String author,String subject,int yearwritten){
super(author,subject,yearwritten);
numpages=pages;
name=n;
ishardcover=hardcover;
}
public void writeExternal(ObjectOutput out) throws IOException{
out.writeObject(author);
out.writeObject(subject);
out.writeInt(yearwritten);
out.writeInt(numpages);
out.writeObject(name);
out.writeBoolean(ishardcover);
}
public void readExternal(ObjectInput in)
throws IOException,ClassNotFoundException{
author=(String)in.readObject();
subject=(String)in.readObject();
yearwritten=in.readInt();
numpages=in.readInt();
name=(String)in.readObject();
ishardcover=in.readBoolean();
}
public String toString(){
return("Name:"+name+"/n"+"Author:"+author+"/n"+"Pages:"
+numpages+"/n"+"Subject:"+subject+"/n"+"Year:"+yearwritten+"/n");
}
}
/* ReadingMaterial2.java */
import java.io.*;
import java.lang.String;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.lang.ClassNotFoundException;
import java.io.IOException;
import java.io.ObjectOutput;
class ReadingMaterial2 {
String author;
String subject;
int yearwritten;
public ReadingMaterial2(){}
ReadingMaterial2(String auth,String sub,int year){
author=auth;
subject=sub;
yearwritten=year;
}
}
结果如下:
Printing original book..
Name:How to Serialize
Author:R.R
Pages:100
Subject:Serialization
Year:97
Printing new book..
Name:How to Serialize
Author:R.R
Pages:100
Subject:Serialization
Year:97
Both original and new should be the same
5.演进类的序列化
/* EvolutionExampleOriginalClass.java */
import java.io.*;
import java.util.*;
public class EvolutionExampleOriginalClass3{
public static void main(String args[]){
boolean serialize=false;
boolean deserialize=false;
if(args.length==1){
if(args[0].equals("-d")){
deserialize=true;
} else if(args[0].equals("-s")){
serialize=true;
} else {
usage();
System.exit(0);
}
}else {
usage();
System.exit(0);
}
AClass serializeclass=new AClass(10,"serializedByOriginalClass");
AClass deserializeclass=null;
if(serialize){
try{
FileOutputStream fo=new FileOutputStream("evolve.tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(serializeclass);
so.flush();
}catch(Exception e){
System.out.println(e);
System.exit(1);
}
}
if(deserialize){
try{
FileInputStream fi=new FileInputStream("evolve.tmp");
ObjectInputStream si=new ObjectInputStream(fi);
deserializeclass=(AClass)si.readObject();
}catch(Exception e){
System.out.println(e);
System.exit(1);
}
System.out.println("Now printing deserialzed object's name:");
System.out.println();
System.out.println("name:"+deserializeclass.name);
System.out.println();
}
}
static void usage(){
System.out.println("Usage:");
System.out.println(" -s(in order to serialize)");
System.out.println(" -d(in order to deserialize)");
}
}
class AClass implements Serializable{
private int num;
String name;
AClass(int n,String s){
num=n;
name=s;
}
private void writeObject(ObjectOutputStream s) throws IOException{
s.defaultWriteObject();
}
private void readObject(ObjectInputStream s)
throws IOException,ClassNotFoundException{
s.defaultReadObject();
}
public String toString(){
return("Name:"+name+"/n"+"Num"+num+"/n");
}
}
/* EvolutionExampleEvolvedClass3.java */
import java.io.*;
import java.util.*;
public class EvolutionExampleEvolvedClass3{
public static void main(String args[]){
boolean serialize=false;
boolean deserialize=false;
if(args.length==1){
if(args[0].equals("-d")){
deserialize=true;
} else if(args[0].equals("-s")){
serialize=true;
} else {
usage();
System.exit(0);
}
}else {
usage();
System.exit(0);
}
AClass serializeclass=new AClass(20,"serializedByOriginalClass");
AClass deserializeclass=null;
if(serialize){
try{
FileOutputStream fo=new FileOutputStream("evolve.tmp");
ObjectOutputStream so=new ObjectOutputStream(fo);
so.writeObject(serializeclass);
so.flush();
}catch(Exception e){
System.out.println(e);
System.exit(1);
}
}
if(deserialize){
try{
FileInputStream fi=new FileInputStream("evolve.tmp");
ObjectInputStream si=new ObjectInputStream(fi);
deserializeclass=(AClass)si.readObject();
}catch(Exception e){
System.out.println(e);
System.exit(1);
}
System.out.println("Now printing deserialzed object's name:");
System.out.println();
System.out.println("name:"+deserializeclass.name+"/n"+deserializeclass.num+"/n"+deserializeclass.b);
System.out.println();
}
}
static void usage(){
System.out.println("Usage:");
System.out.println(" -s(in order to serialize)");
System.out.println(" -d(in order to deserialize)");
}
}
class AClass implements Serializable{
int num;
boolean b;
String name;
AClass(int n,String s){
num=n;
name=s;
boolean b=false;
}
private void writeObject(ObjectOutputStream s) throws IOException{
s.defaultWriteObject();
}
private void readObject(ObjectInputStream s)
throws IOException,ClassNotFoundException{
s.defaultReadObject();
}
}
结果如下:
Now printing deserialzed object's name:
name:serializedByOriginalClass
20
false