ITEEDU

Java Gossip: ObjectInputStream、ObjectOutputStream

在Java这样支持对象导向的程序中撰写程序,很多数据都是以对象的方式存在,在程序运行过后,您会希望将这些数据加以储存,以供下次执行程序时使用,这时您可以使用ObjectInputStream、ObjectOutputStream来进行这项工作。

要被储存的对象必须实作Serializable接口,说是实作,其实Serializable中并没有规范任何必须实作的方法,所以这边所谓实作的意义,其实像是对对象贴上一个标志,代表该对象是可以序列化的(Serializable)。

一个实作的例子如下所示:

Student.java
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的型态传回,您必须将之转换为对象原来的型态,才能正确的操作被读回的对象,下面这个程序示范了如何简单的储存对象至档案中,并将之再度读 回:

ObjectStreamDemo.java
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 {}
									};

将对象写出或读入并不仅限于档案存取,您也可以用于网络的数据传送,例如传送整个对象数据或是影像档案。