一、序列化和反序列化 点击此处返回总目录 二、ObjectOutputStream序列化流 三、ObjectInputStream反序列化流 四、注意事项:静态成员不能序列化 五、transient关键字:阻止某个成员变量序列化 六、Serializable接口 七、序列化中的序列号冲突问题及解决 一、序列化和反序列化 有时候需要把对象中的数据写到硬盘上保存起来。 将对象中的数据,以流的形式,写入到文件中保存起来,这个过程叫做对象的序列化。用到ObjectOutputStream流。 将硬盘中的文件,以流的形式,读取出来,还原成对象。这个过程叫做对象的反序列化。用到ObjectInputStream流。 ObjectOutputStream 将对象写到文件中,实现对象的序列化。 ObjectInputStream 将对象从文件读出来,实现对象的反序列化。 二、ObjectOutputStream 序列化流 1)构造方法: ObjectOutputStream(OutputStream out) //传递任意的字节输出流。ObjectOutputStream将对象中的数据写到字节输出流,然后字 节输出流写入到文件。 2)方法: void writeObject(Object obj) //写对象。参数可以是任意的对象。 3)使用: 首先编写Person类,要实现Serializable接口。未实现此接口的类将无法使其任何状态序列化或反序列化。也就是说,如果不实现这个接口,对象序列化流就没法使用。令人欣慰的是,serializable接口下面没有任何方法需要重写。 Person.java
package cn.itcast.demo04; import java.io.Serializable; public class Person implements Serializable { //实现Serializable接口。 private String name; private int age; public Person() {} public Person(String name, int age) { super(); 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
//Test.java
package cn.itcast.demo04; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class Test { public static void main(String[] args) throws IOException{ Person p = new Person("lijing",22); FileOutputStream fos = new FileOutputStream("e:\\person.txt"); //字节输出流用于写文件。 ObjectOutputStream obj = new ObjectOutputStream(fos); obj.writeObject(p); //写对象。 obj.close(); } } |
运行之后,对象就写到文件中了,但是打开并看不懂。 三、ObjectInputStream 反序列化流 1)构造方法: ObjectInputStream(InputStream in) //ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。 2)方法: Object readObject() //从ObjectInputStream读取对象。 3)使用: Person.java
package cn.itcast.demo04; import java.io.Serializable; public class Person implements Serializable { //同样要实现Serializable接口,不实现则报错。 private String name; private int age; public Person() {} public Person(String name, int age) { super(); 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
//Test.java
package cn.itcast.demo04; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Test { public static void main(String[] args) throws IOException,ClassNotFoundException{ FileInputStream fis = new FileInputStream("e:\\person.txt"); //person.txt是序列化的文件,不是随便一个文件。 ObjectInputStream ois = new ObjectInputStream(fis); Person p = new Person(); p = (Person)ois.readObject(); System.out.println(p); //Person [name=lijing, age=22] } } |
四、注意事项:静态成员不能序列化 将Person.java中的age改为静态变量,首先将Person对象序列化,然后反序列化: Person.java
package cn.itcast.demo04; import java.io.Serializable; public class Person implements Serializable{ private String name; private static int age; //首先,将age改为静态变量。 public Person() {} public Person(String name, int age) { super(); 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
Test.java
package cn.itcast.demo04; import java.io.FileInputStream; 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 IOException,ClassNotFoundException{ //writeObject(); //然后执行writeObject()方法,完成序列化 readObject(); //最后执行readObject()方法,反序列化 } public static void writeObject() throws IOException{ Person p = new Person("lijing",22); FileOutputStream fos = new FileOutputStream("e:\\person.txt"); ObjectOutputStream obj = new ObjectOutputStream(fos); obj.writeObject(p); obj.close(); } public static void readObject() throws IOException,ClassNotFoundException{ FileInputStream fis = new FileInputStream("e:\\person.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person p = new Person(); p = (Person)ois.readObject(); System.out.println(p); //Person [name=lijing, age=0] } } |
执行结果,发现age=0,不是22。 为什么加了静态之后就有问题呢?因为我们这里讲的是对象序列化,跟对象有关系。前面讲过,静态成员不属于对象,而是属于自己的类。所以对象当中不包含静态数据。所以静态数据不能序列化。也就是age=22没有写到文件中。因此也就读不出来。为什么是0呢,因为成员变量无论静态还是非静态都有默认值。当创建对象的时候默认值就是0。 五、transient关键字:阻止成员变量序列化 有时对于非静态成员变量,也希望他不要序列化。这时候就用到transient关键字。 瞬态关键字用处比较单一,只有这一种用处。而且用法也很单一,只能修饰成员变量。 //Person.java
package cn.itcast.demo04; import java.io.Serializable; public class Person implements Serializable{ private String name; private transient int age; public Person() {} public Person(String name, int age) { super(); 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
//Test.java
package cn.itcast.demo04; import java.io.FileInputStream; 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 IOException,ClassNotFoundException{ //writeObject(); //首先运行这个方法 readObject(); //然后运行这个方法 } public static void writeObject() throws IOException{ Person p = new Person("lijing",22); FileOutputStream fos = new FileOutputStream("e:\\person.txt"); ObjectOutputStream obj = new ObjectOutputStream(fos); obj.writeObject(p); obj.close(); } public static void readObject() throws IOException,ClassNotFoundException{ FileInputStream fis = new FileInputStream("e:\\person.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Person p = new Person(); p = (Person)ois.readObject(); System.out.println(p); //Person [name=lijing, age=0] } } |
运行结果可以看到:age= 0 ,不是22,说明已经阻止了age序列化。 六、Serializable接口 serializable接口的源码如下: public interface Serializable{ } 里面什么也没有。这种接口称为标记型接口。说的直白一点,凡是里面没有方法的接口,都称为标记型接口。 标记型接口的作用就是做一个标记。比如,一旦被Serizlizable标记上了,就表示可以做序列化了。 当序列化的时候看到这个类有标记,才可以序列化。如果没有标记,就不能序列化。 七、序列化中的序列号冲突问题及解决 1)序列化问题产生的原因 进行下列的操作: 1.调用writeObject()方法将Person对象序列化 2.修改Person类的源码。比如将private int age改为public int age 3.调用readObject()方法,尝试反序列化 结果报错: Exception in thread "main" java.io.InvalidClassException: cn.itcast.demo04.Person; local class incompatible: stream classdesc serialVersionUID = 3834510344803489759, local class serialVersionUID = 3737332643963348207 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at cn.itcast.demo04.Test.readObject(Test.java:30) at cn.itcast.demo04.Test.main(Test.java:12) 写完对象,然后把源码改了,再读就读不回来了,这就是序列号冲突问题。serivalVersionUID就是序列号。 序列化冲突问题怎么来的呢? 首先写类: class Person implements Serializable{ private String name; private int age; } 之后,保存,eclipse会帮我们进行编译,生成Person.class。当实现了Serializable时,编译的时候会根据类的定义算一个序列号,比如1234567。所以Person.class文件中除了有编译后的对象数据之外,还有一个序列号1234567。 当调用ObjectOutputStream写对象时,会把对象数据和序列号1234567写到Person.txt中。 之后修改了类,保存,会重新编译,eclipse又重新生成了一个Person.class文件,序列号也是重新算的,比如2222222。 当调用ObjectInputStream读对象的时候,会将文件Person.txt中的序列号(1234567)与class文件中的序列号(2222222)进行比较。如果一样就读取成功,如果不一样就读取失败。 综上,新编译的class文件中的序列号与文件Person.txt中保存的序列号不同,导致异常。 2)如何解决序列化冲突问题 如何避免序列号冲突呢?只要保证无论修改还是不修改类,编译后都有相同的序列号即可。即使修改了源代码,也不重新计算序列号。序列号一旦确定,永远不变。 查看API文档中Serializable接口的描述:
序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException 。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID: ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; |
因此只要在可序列化类中增加static final long serialVersionUID = 42L即可。 //Person.java
package cn.itcast.demo04; import java.io.Serializable; public class Person implements Serializable{ private String name; public int age; static final long serialVersionUID = 42L; //只有后面的数值可以改,其他都不能改。 public Person() {} public Person(String name, int age) { super(); 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; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
这样就可以解决序列号冲突问题了。 修改之后,调用写,将private改成public保存,调用读。还是可以得到age = 22。 |