两个Java进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节(Byte)序列,才能在网络上传送;接收方将字节序列恢复为Java对象后便可以使用此对象。
把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列,这是RMI和Hessian的基础。
一. JDK中序列化的API
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的对象参数进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为(即自我定制实例变量写入字节流的顺序,除类头外)。要序列化的类实现Serializable接口只是一个标识,并没有实现任何接口的方法。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如FileOutputStream;
2) 通过对象输出流的writeObject()方法写对象。
对象反序列化的步骤如下:
1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如FileInputStream和InputStream;
2) 通过对象输入流的readObject()方法读取对象。
下面来看例子:
待序列化类包括两个类,User.java和Account.java。其中User包含一个Account类型的成员变量。
public class User implements Serializable{
/**
* 序列化对象1
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
private Account account;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
@Override
public String toString(){
return(" name :"+this.getName() +
"\n age is :"+ this.getAge() +
"\n sex is :"+this.getSex() +
"\n with account "+ this.getAccount()
);
}
}
Account.java
public class Account implements Serializable{
/**
* 序列化对象2
*/
private static final long serialVersionUID = 1L;
private int current;
public Account(){
SAXReader rd = new SAXReader();
File f = new File("account.xml");
try {
Document document = rd.read(f);
Node node = document.selectSingleNode("/account/current");
this.setCurrent(Integer.parseInt(node.getText()));
} catch (DocumentException e) {
System.out.println("Parsing XML failed!");
e.printStackTrace();
}
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public String toString(){
return ("Currenly have "+this.getCurrent());
}
}
Account在初始化时,首先读取下面的XML文件来set成员变量。
<?xml version="1.0" encoding="GB2312"?>
<account>
<current>5000</current>
</account>
主函数在TestSerializable.java中,它做的事情是new一个user出来,序列化后保存到一个文件里,然后再读取这个文件反序列化:
public class TestSerializable {
public static void writeObject(Object o) throws IOException {
File f = new File ("user.tmp");
if(f.exists()){
f.delete();
}
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(o);
oos.flush();
fos.close();
oos.close();
}
public static Object readObject(File f) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
return ois.readObject();
}
public static void main(String[] args){
User u = new User();
u.setName("zhangchao");
u.setAge(10);
u.setSex("m");
Account account = new Account();
u.setAccount(account);
try {
writeObject(u);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
File f = new File("user.tmp");
if(!f.exists()){
System.exit(-1);
}
try {
User oldUser = (User) readObject(f);
System.out.println(oldUser);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
来看输出结果:
name :zhangchao
age is :10
sex is :m
with account Currenly have 5000
注意待与系列化的类Account和User必须都实现Serializable接口,否则会报NotSerializableException异常。
当然这只是本地测试,现在进行远程传输。将序列化好后的文件放在一个tomcat应用的根目录下,此例中是(user.tmp)。
TestRemoteSerializable.java的源码如下:
public class TestRemoteSerializable {
public static void main(String[] args) throws IOException{
URL url = new URL("http://localhost:8080/workshop/user.tmp");
InputStream is = url.openStream();
ObjectInputStream ois = new ObjectInputStream(is);
User u;
try {
u = (User) ois.readObject();
System.out.println(u);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出结果:
name :zhangchao
age is :10
sex is :m
with account Currenly have 5000
最后关于序列化还有三点需要注意:
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;