对象序列化的目标是将对象保存到磁盘中,或允许网络中直接传输对象。对象序列化允许把内存中的Java对象转换为平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。而其它程序获得了这种二进制流,都可以用反序列化将二进制流恢复成原来的Java对象。
1.使用对象流实现序列化
如果需要将某个对象序列化,这个类应该实现Serializable接口或者Externalizable接口之一。这里首先介绍实现Serializable接口,只需要待序列化的类实现该接口即可。可看如下程序:
Person类:
import java.io.Serializable;
public class Person implements Serializable {
public int age;
public String name;
public Person(int age,String name) {
this.age=age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
序列化:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
//序列化
Person person = new Person(11, "Bob");
ByteArrayOutputStream os = new ByteArrayOutputStream();//定义一个字节数组输出流
ObjectOutputStream outputStream = new ObjectOutputStream(os);//对象输出流
outputStream.writeObject(person);//将对象写入到字节数组输出,实现序列化。
byte[] personByte = os.toByteArray();
System.out.println(Arrays.toString(personByte));
}
}
返回这个序列化后的字节数组:
[-84, -19, 0, 5, 115, 114, 0, 15, 115, 101, 114, 105, 97, 98, 108, 101, 46, 80, 101, 114, 115, 111, 110, -84, -127, 59, -107, 84, -82, -111, -29, 2, 0, 2, 73, 0, 3, 97, 103, 101, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 120, 112, 0, 0, 0, 11, 116, 0, 3, 66, 111, 98]
2.反序列化
将以上序列化之后再进行反序列化,恢复Java对象,代码如下:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//序列化
Person person = new Person(11, "Bob");
ByteArrayOutputStream os = new ByteArrayOutputStream();//定义一个字节数组输出流
ObjectOutputStream outputStream = new ObjectOutputStream(os);//对象输出流
outputStream.writeObject(person);//将对象写入到字节数组输出,实现序列化。
byte[] personByte = os.toByteArray();
System.out.println(Arrays.toString(personByte));
//反序列化
ByteArrayInputStream is = new ByteArrayInputStream(personByte);//字节组输入流
ObjectInputStream inputStream = new ObjectInputStream(is);
Person person2 = (Person)inputStream.readObject();
System.out.println(person2.name+":"+person2.age);
}
}
运行结果:
[-84, -19, 0, 5, 115, 114, 0, 15, 115, 101, 114, 105, 97, 98, 108, 101, 46, 80, 101, 114, 115, 111, 110, -84, -127, 59, -107, 84, -82, -111, -29, 2, 0, 2, 73, 0, 3, 97, 103, 101, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 120, 112, 0, 0, 0, 11, 116, 0, 3, 66, 111, 98]
Bob:11
3.序列化文件存储唯一性
即一个类多次序列化,但只有一个其对应的序列化存储区,无论其被多少个对应引用,都只有一个结果 。具体示例如下,首先将两个相同Person对象写入文件,然后反序列化出两个对象,进行比较:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//序列化
Person person = new Person(11, "Bob");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
oos.writeObject(person);
oos.writeObject(person);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
Person p1 = (Person)ois.readObject();
Person p2 = (Person)ois.readObject();
System.out.println(p1==p2);
}
}
运行结果:
true
以上结果说明:如果多次序列化同一个Java对象时,只有第一次序列化时才会把该Java对象转换成字节序列并输出。但这也有一定问题,当程序序列化一个可变对象时,只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,当程序再次调用writeObject()方法时,程序只是输出前面的序列化编号,即使后面该对象的实例变量已被改变,改变的实例变量也不会输出。例子如下:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//序列化
Person person = new Person(11, "Bob");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
oos.writeObject(person);
person.setAge(12);
oos.writeObject(person);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
Person p1 = (Person)ois.readObject();
Person p2 = (Person)ois.readObject();
System.out.println(p2.age);
System.out.println(p1==p2);
}
}
运行结果:
11
true
以上结果说明,即使age变量发生了改变,但两次person对象只认准第一次writeObject()方法的序列化结果。
4.关键字transient
transient修饰的实例变量在序列化过程中将被完全隔离在序列化机制之外,从而导致在反序列化恢复Java对象时无法取得该实例变量值。如下例子所示:
import java.io.Serializable;
public class Person implements Serializable {
public int age;
public String name;
public transient int height;
public Person(int age,String name) {
this.age=age;
this.name = name;
}
public Person(int age,String name,int height) {
this.age = age;
this.name = name;
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TransientTest {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));
Person person = new Person(11,"Tom",180);
oos.writeObject(person);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt"));
Person person2 = (Person)ois.readObject();
System.out.println(person2.age);
System.out.println(person2.height);
}
}
运行结果如下:
11
0
5.自定义序列化机制-Externalizable接口
该接口继承了Serializable接口,并且扩展了自定义序列化类的功能。
从下图可以看出,实现Externalizable接口,需要实现两个方法:readExternal()和writeExternal(),分别对应着序列化类的反序列化和序列化操作。
根据源码上注释可知:
void writeExternal(ObjectOutput out) throws IOException:需要序列化的类实现writeExternal()方法来保存对象的状态。该方法调用DataOutput的方法来保存基本类型的实例变量值,调用ObjectOutput的writeObject()方法来保存引用类型的实例变量值。
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException:需要序列化的类实现该方法来实现反序列化。该方法调用DataInput的方法来恢复基本类型的实例变量值,调用ObjectInput的readObject()方法来恢复引用类型的实例变量值。
以下是代码示例:
Person类:
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class Person implements Externalizable {
public String name;
public int age;
public Person() {}
public Person(String name,int age) {
this.name =name;
this.age =age;
}
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;
}
//通过该方法来保存对象的状态。该方法调用DataOutput的方法来保存基本类型的实例变量值,调用ObjectOutput的writeObject()方法来保存引用类型的实例变量值,调用ObjectOutput的writeObject()方法来保存引用类型的实例变量值
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(new StringBuilder(name).reverse());
out.writeInt(age);
}
//需要序列化的类实现该方法来实现反序列化。该方法调用DataInput的方法来恢复基本类型的实例变量值,调用ObjectInput的readObject()方法来恢复引用类型的实例变量值
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
this.name = ((StringBuilder)(in.readObject())).reverse().toString();
this.age = in.readInt();
}
}
测试类:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
//序列化
Person person = new Person("Bob",12);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
oos.writeObject(person);
person.setAge(13);
oos.writeObject(person);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
Person person2 = (Person)ois.readObject();
Person person3 = (Person)ois.readObject();
System.out.println(person2.name+":"+person2.age);
System.out.println(person2==person3);
}
}
运行结果:
Bob:12
true
注意:实现Externalizable接口的类反序列化时,程序会先使用public的无参数构造器创建实例,然后执行readExternal()方法进行反序列化。因此实现Externalizable接口的类必须提供一个public的无参数构造器。