在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 {} };
将对象写出或读入并不仅限于档案存取,您也可以用于网络的数据传送,例如传送整个对象数据或是影像档案。