在Java这样支持对象导向的程序中撰写程序,很多数据都是以对象的方式存在,在程序运行过后,您会希望将这些数据加以储存,以供下次执行程序时使用,这时您可以使用ObjectInputStream、ObjectOutputStream来进行这项工作。
要被储存的对象必须实作Serializable接口,说是实作,其实Serializable中并没有规范任何必须实作的方法,所以这边所谓实作的意义,其实像是对对象贴上一个标志,代表该对象是可以序列化的(Serializable)。
一个实作的例子如下所示:
package onlyfun.caterpillar;
import java.io.*;
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int score;
public Student() {
name = "N/A";
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public void setName(String name) {
this.name = name;
}
public void setScore(int score) {
this.score = score;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public void showData() {
System.out.println("name: " + name);
System.out.println("score: " + score);
}
}
您要注意到serialVersionUID,这代表了可序列化对象的版本, 如果您没有提供这个版本讯息,则会自动依类名称、实现的接口、成员等讯息来产生,如果是自动产生的,则下次您更改了Student类,则自动产生的 serialVersionUID也会跟着变更,当反序列化时两个serialVersionUID不相同的话,就会丢出 InvalidClassException,如果您想要维持版本讯息的一致,则要显式宣告serialVersionUID。
ObjectInputStream、ObjectOutputStream为InputStream、OutputStream加上了可以让使用者写入 对象、读出对象的功能,在写入对象时,我们使用writeObject()方法,读出对象时我们使用readObject()方法,被读出的对象都是以 Object的型态传回,您必须将之转换为对象原来的型态,才能正确的操作被读回的对象,下面这个程序示范了如何简单的储存对象至档案中,并将之再度读 回:
package onlyfun.caterpillar;
import java.io.*;
import java.util.*;
public class ObjectStreamDemo {
public static void writeObjectsToFile(
Object[] objs, String filename) {
File file = new File(filename);
try {
ObjectOutputStream objOutputStream =
new ObjectOutputStream(
new FileOutputStream(file));
for(Object obj : objs) {
objOutputStream.writeObject(obj);
}
objOutputStream.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
public static Object[] readObjectsFromFile(
String filename)
throws FileNotFoundException {
File file = new File(filename);
if(!file.exists())
throw new FileNotFoundException();
List list = new ArrayList();
try {
FileInputStream fileInputStream =
new FileInputStream(file);
ObjectInputStream objInputStream =
new ObjectInputStream(fileInputStream);
while(fileInputStream.available() > 0) {
list.add(objInputStream.readObject());
}
objInputStream.close();
}
catch(ClassNotFoundException e) {
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
return list.toArray();
}
public static void appendObjectsToFile(
Object[] objs, String filename)
throws FileNotFoundException {
File file = new File(filename);
if(!file.exists())
throw new FileNotFoundException();
try {
ObjectOutputStream objOutputStream =
new ObjectOutputStream(
new FileOutputStream(file, true)) {
protected void writeStreamHeader()
throws IOException {}
};?
for(Object obj : objs) {
objOutputStream.writeObject(obj);
}
objOutputStream.close();
}
catch(IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Student[] students = {new Student("caterpillar", 90),
new Student("justin", 85)};
// 写入新档
writeObjectsToFile(students, "data.dat");
try {
// 读取档案数据
Object[] objs = readObjectsFromFile("data.dat");
for(Object obj : objs) {
((Student) obj).showData();
}
System.out.println();
students = new Student[2];
students[0] = new Student("momor", 100);
students[1] = new Student("becky", 100);
// 附加至档案
appendObjectsToFile(students, "data.dat");
// 读取档案数据
objs = readObjectsFromFile("data.dat");
for(Object obj : objs) {
((Student) obj).showData();
}
}
catch(FileNotFoundException e) {
e.printStackTrace();
}
}
}
对象被写出时,会写入对象的类别型态、类别署名(Class signature),static与被标志为transient的成员则不会被写入。
在这边注意到以附加的形式写入数据至档案时,在试图将对象附加至一个先前已写入对象的档案时,由于ObjectOutputStream在 写入数据时,还会加上一个特别的标示头,而读取档案时会检查这个标示头,如果一个档案中被多次附加对象,那么该档案中会有多个标示头,如此读取检查时就会 发现不一致,这会丢出StreamCorrupedException,为此,您重新定义ObjectOutputStream的writeStreamHeader()方法,如果是以附加的方式来写入对象,就不写入标示头:
ObjectOutputStream objOutputStream =
new ObjectOutputStream(
new FileOutputStream(file, true)) {
protected void writeStreamHeader()
throws IOException {}
};
将对象写出或读入并不仅限于档案存取,您也可以用于网络的数据传送,例如传送整个对象数据或是影像档案。