java序列化与反序列化总结

来源:互联网 发布:js点击按钮隐藏div 编辑:程序博客网 时间:2024/05/29 09:31

1 含义

序列化:把java对象转变成一组字节数组。

反序列化:从一组字节流中恢复成一个java对象。

注意:序列化过程仅保存对象的成员变量。


2、使用时机

1)需要将内存中的对象”持久化”的存储在硬盘上(文件或数据库中)

2)需要网络通信时:先将对象序列化为一串二进制字节流,再进行传输;接收端,先接收二进制流,再从中反序列化出对象。


3、java中实现序列化的前提

3.1 两个重要的类

ObjectOutputStream

对象输出流,即通过该类将对象序列化并输出到指定的输出流中。

构造:

         ObjectOutputStream(OutputStream out)

重要方法:

         void writeObject(Object obj),序列化obj对象并写入到输出流中

 

ObjectInputStream

对象输入流,从一个输入流中读取字节序列,并反序列化为一个对象。

构造:

         ObjectInputStream(InputStream in)

重要方法:

         Object readObject(),反序列化出对象,并返回。


3.2 两个重要的接口

Serializable

该接口是一个标记接口,也就是空接口,没有任何的方法。

只有实现了Serializable接口的类才能被java的序列化机制处理,否则会抛出异常。

 

Externalizable

实现了Externalizable接口的类也能被序列化。

Externalizable接口继承自Serializable接口。

Externalizable接口有两个重要方法:

         void readExternal(ObjectInput in)

         void writeExternal(ObjectOutput out)

实现了Externalizable接口的类,必须override该接口的两个方法。在序列化对象的时候,java会调用writeExternal()方法来序列化对象;在反序列化时,java会调用readExternal()方法来恢复对象。即是说,通过实现Externalizable接口来进行序列化时,我们可以手动控制对象的哪些成员能被序列化,哪些成员不需要被序列化,更加灵活。


4、java序列化实例

注意:对象序列化是基于字节流的,应该使用InputStream和OutputStream,不能使用基于字符的Reader和Writer。


4.1 实现Serializable接口并采用默认的序列化方式

class Person implements Serializable{private int age;private int height;private String name;private transient String gender;public Person(int age, int height, String name, String gender){this.age = age;this.height = height;this.name = name;this.gender = gender;}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append("年龄:" + age + ", 身高:" + height + ", 姓名:" + name+ ", 性别:" + gender);return sb.toString();}}public static void main(String[] args) {try {// 序列化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.txt"));out.writeObject(new Person(20, 170, "赵李", "男"));out.flush();out.close();// 反序列化对象ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.txt"));Person p = (Person)in.readObject();System.out.println(p.toString());in.close();}  catch (ClassNotFoundException e) {e.printStackTrace();}catch (IOException e) {e.printStackTrace();}}

输出:

年龄:20, 身高:170, 姓名:赵李, 性别:null

分析:

上述代码对Person对象的非transient成员变量进行了序列化与反序列化。


4.2 实现Serializable接口并采用自定义的序列化方式

按4.1中的代码,在Person类中增加了两个私有方法writeObject()和readObject(),main()函数不变:

class Person implements Serializable{private int age;private int height;private String name;private transient String gender;public Person(int age, int height, String name, String gender){this.age = age;this.height = height;this.name = name;this.gender = gender;}// 自定义序列化private void writeObject(ObjectOutputStream out) throws IOException{out. defaultWriteObject ();// 手动序列化transient成员out.writeObject(gender);}// 自定义反序列化private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{in.defaultReadObject();// 手动反序列化transient成员gender = (String)in.readObject();}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append("年龄:" + age + ", 身高:" + height + ", 姓名:" + name+ ", 性别:" + gender);return sb.toString();}}

输出:

年龄:20, 身高:170, 姓名:赵李, 性别:男

分析:

看一下堆栈调用:


在该自定义的序列化方式中,main()中的ObjectOutputStream.writeObject()方法通过一系列调用,最后会调用Person.writeObject()方法序列化Person对象,上图可知,应该是通过反射机制来调用Person.writeObject()处理。

反序列化同理,ObjectInputStream.readObject()最终调用Person.readObject()完成反序列化过程。

在Person自定义的两个私有方法中,可以手动序列化与反序列化transient成员,如上例所示。

疑问:

Person类中若定义为 public void writeObject(ObjectOutputStream out)

或 public voidreadObject(ObjectInputStream in),

则不会调用这两个自定义方法,即是说必须声明为private方法才能被调用,为什么?


4.3 实现Externalnalizable接口并采用自定义的序列化方式

Person类实现Externalnalizable接口,覆写readExternal()和writeExternal()方法实现自定义的序列化和反序列化,main()函数不变,如下:

class Person implements Externalizable{private int age;private int height;private String name;private transient String gender;public Person() {}public Person(int age, int height, String name, String gender){this.age = age;this.height = height;this.name = name;this.gender = gender;}// 序列化实现@Overridepublic void writeExternal(ObjectOutput out) throws IOException {// 手动序列化每个成员out.writeObject(age);out.writeObject(height);out.writeObject(name);out.writeObject(gender); // 可以序列化transient成员}// 反序列化实现@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.age = (int)in.readObject();this.height = (int)in.readObject();this.name = (String)in.readObject();this.gender = (String)in.readObject();}@Overridepublic String toString() {StringBuilder sb = new StringBuilder();sb.append("年龄:" + age + ", 身高:" + height + ", 姓名:" + name+ ", 性别:" + gender);return sb.toString();}}

输出:

年龄:20, 身高:170, 姓名:赵李, 性别:男

分析:

在该种序列化方式中,main()中的ObjectOutputStream.writeObject()最后会调用Person. writeExternal()方法完成序列化过程,反序列化过程类似。

在Person.writeExternal()中,通过对Person成员变量的依次写入完成序列化过程,手动控制每个成员是否需要被序列化,更为灵活。在Person.readExternal()中,按照成员序列化的顺序,依次反序列化出每个成员,完成Person对象的反序列化过程。

注意:

通过实现Externalnalizable接口的序列化方式,必须提供被序列化对象的无参构造函数,上例中,即必须声明 public Person() {} 才行,否则会报如下错误



5 使用java序列化机制需要注意的几点


5.1 serialVersionUID

该字段用来保证对象在序列化和反序列化过程中的一致性,即是说,只有相同serialVersionUID值的同类对象才能相互序列化和反序列化,若有两个类,其成员和方法完全形同,但其serialVersionUID不同,是无法相互序列化和反序列化的。

若无特殊要求,serialVersionUID取默认值即可,即:

private static finallong serialVersionUID = 1L;


5.2 static、transient成员

static成员属于某个类,而非某个特定的对象,序列化机制会忽略static成员,不会保存static成员的状态。

默认的序列化机制也会忽略transient成员,但自定义的序列化方式可以手动序列化和反序列化transient成员,如上述的例子所示。


5.3 序列化中的包含问题

若一个被序列化的对象中包含其他对象,则被包含的对象也会被序列化。可以理解为java的序列化机制是一个”深拷贝”,它会将待序列化对象的整个对象图全部序列化掉。


5.4 序列化中的继承问题

若父类已实现序列化,则子类自动序列化,不需要显示实现Serializable接口。

若父类未实现序列化,但子类实现了序列化,序列化某个子类对象,再反序列化后,子类对象中某些继承自在父类中定义的成员变量,其值与序列化前的值可能不同。因为父类没有实现序列化,在反序列化时,java会先调用父类的无参构造,再调用子类的构造函数。那些在父类中定义的成员变量,其值是调用父类无参构造后的值,与序列化前实际的值可能有差异。

建议在使用java的序列化与反序列化时,尽s量避免类继承。


6 参考文章

http://blog.csdn.net/jiangwei0910410003/article/details/18989711/#t7

http://blog.csdn.net/wangloveall/article/details/7992448/

http://blog.csdn.net/endlu/article/details/51178143

http://www.oschina.net/translate/serialization-in-java?lang=chs&page=1#

http://www.2cto.com/kf/201405/305380.html
原创粉丝点击