java 序列化与反序列化

什么是序列化

(1)序列化是将对象转变为字节序列的过程,反序列化则是将字节序列恢复为对象的过程。

(2)对象序列化保存的是对象的状态,即它的成员变量;

(3)对象的持久化存储(写文件),网络传输对象,或者使用RMI都会用到对象序列化。

 


JAVA 提供的操作序列化的接口

(1)Java 主要提供给了两个接口实现对象的序列化和反序列化,java.io.ObjectInputStream的readObject()方法 和 java.io.ObjectOutputStream 的writeObject(Object obj)方法;

(2)只有实现Serializable或Externalizable接口的类的对象才能被序列化;否则会抛出java.io.NotSerializableException异常。

 


JAVA对象序列化示例

 

(1)类实现 Serializable接口

 类中未定义 writeObject(Object obj)和readObject方法,那么按照默认的序列化方式实现序列化和反序列化。

 

 1 package test3;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Student implements Serializable {
 6 
 7     /** 
 8      * 序列化UID唯一标识.
 9      * 反序列化时,如果UID不一致,就无法实现反序列化,并且将抛出InvalidClassException.[方法,域不能减,可以增,向后兼容]
10      * 如果不显式指定,VM默认生成一个(耗费资源),但反序列化时会出问题.
11      */
12     private static final long serialVersionUID = -6552367376730553255L;
13 
14     private String name;
15     private int age;
16     private Gender gender;
17     
18     public Student(String name, int age, Gender gender) {
19         this.name = name;
20         this.age = age;
21         this.gender = gender;
22     }
23 
24     @Override
25     public String toString() {
26         return "Student [name=" + name + ", age=" + age + ", gender=" + gender + "]";
27     }
28     
29 }
Student.java

 

1 package test3;
2 
3 public enum Gender {
4 
5     MALE,FEMALE;
6     
7 }
Gender.java

 

 1 package test3;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 public class SimpleSerializable {
11 
12     public static void main(String[] args) throws IOException, ClassNotFoundException {
13         Student student = new Student("Lily", 18, Gender.FEMALE);
14         //序列化student对象
15         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\file1")));
16         //ObjectOutputStream oos2 = new ObjectOutputStream(new ByteArrayOutputStream());//字节流形式
17         oos.writeObject(student);
18         oos.close();
19         
20         //反序列化
21         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\file1")));
22         Object stu = ois.readObject();
23         ois.close();
24         System.out.println(stu.toString());
25     }
26     
27 }
SimpleSerializable.java
 

以上代码展示了如何序列化对象到一个文件中并从文件中反序列化的过程。

序列化的过程:

首先创建 ObjectOutputStream 对象,该对象可以包装其他输出流,比如文件输出流;

调用对象输出流的writeObject(Object obj)方法,可以将对象写入到输出流中。

关闭流。结束。

对象持久化到文件中的过程结束。

反序列化的过程:

首先创建ObjectInputStream对象,类似于ObjectOutputStream;

调用对象输入流的readObject()方法,读对象到输入流中。返回字节序列转化的对象。

关闭流;结束。

输出:

Student [name=Lily, age=18, gender=FEMALE]
result

 

 

 

 类中定义了 writeObject(Object obj)和readObject方法,那么按照自定义的序列化方实现式序列化和反序列化。

在Student.java添加如下两个方法:

 

 1     private void writeObject(ObjectOutputStream out) throws IOException{
 2         out.defaultWriteObject();
 3         //...
 4         System.out.println("自定义序列化方法");
 5     }
 6     
 7     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
 8         in.defaultReadObject();
 9         //...
10         System.out.println("自定义反序列化方法");
11     }
user-dined_read_write_Object
 

输出结果:

自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=18, gender=FEMALE]
result2

 

(2)transient 关键字

  当某个成员变量声明为transient后,默认的序列化机制就会忽略该变量。

将age字段声明为transient,

1     private static final long serialVersionUID = -6552367376730553255L;
2 
3     private String name;
4     transient private int age;
5     private Gender gender;
transient
 

输出 age=0:

自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=0, gender=FEMALE]
result3

 

此时我们可以选择单独传输某个字段;修改writeObject和readObject方法:

 

 1     private void writeObject(ObjectOutputStream out) throws IOException{
 2         out.defaultWriteObject();
 3         out.writeInt(19);
 4         System.out.println("自定义序列化方法");
 5     }
 6     
 7     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException{
 8         in.defaultReadObject();
 9         this.age = in.readInt();
10         System.out.println("自定义反序列化方法");
11     }
 

结果:

自定义序列化方法
自定义反序列化方法
Student [name=Lily, age=19, gender=FEMALE]
result4
 

默认形式序列化和反序列化后,又单独传输了age 字段,因此age=19;

补充:除了上面提到的两个方法外:

private void writeObject(java.io.ObjectOutputStream out) throws IOException ;

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;

还有其他三个方法,可供我们定制自己的序列化反序列化过程:

private void readObjectNoData() throws ObjectStreamException;

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

readObjectNoData() :用于初始化反序列化对象,当发生一些情况导致反序列化对象不能获得数据时调用;

writeReplace() 指派其他对象写入序列化的流中;

readResolve()返回的对象替换反序列化创建的实例;

 

readResolve() 常用于单例模式中;示例:

修改Student.java,添加instanceHoder:

1     private static class instanceHoder{
2         private static final Student instance = new Student("Simon", 12, Gender.MALE);
3     }
4     
5     public static Student getInstance(){
6         return instanceHoder.instance;
7     }
Student.java

 

1     private Object writeReplace() throws ObjectStreamException{
2         System.out.println("writeReplace, 调用");
3         return this;
4     }
5     
6     private Object readResolve() throws ObjectStreamException{
7         System.out.println("readResolve, 调用");
8         return this;
9     }
Student.java
 

修改SimpleSerial.java:

 

 1 public class SimpleSerializable {
 2 
 3     public static void main(String[] args) throws IOException, ClassNotFoundException {
 4         
 5         //Student student = new Student("Lily", 18, Gender.FEMALE);
 6         
 7         Student student = Student.getInstance();
 8         //序列化student对象
 9         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\file1")));
10         oos.writeObject(student);
11         oos.close();
12         
13         //反序列化
14         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\file1")));
15         Object stu = ois.readObject();
16         ois.close();
17         System.out.println(stu.toString());
18         
19         //比较反序列化后的对象s 是否符合单例
20         System.out.println(stu==student);
21     }
22     
23 }
SimpleSerial.java
 

结果输出:

writeReplace, 调用
自定义序列化方法
自定义反序列化方法
readResolve, 调用
Student [name=Simon, age=19, gender=MALE]
false
result5
 

可以看到,s==student返回false,也就是说反序列化后得到的Student对象并不是唯一的instance,因此这样写单例模式是失败的;

修正:

1     private Object readResolve() throws ObjectStreamException{
2         System.out.println("readResolve, 调用");
3         return instanceHoder.instance;
4     }
readResolve
 

再次运行:

writeReplace, 调用
自定义序列化方法
自定义反序列化方法
readResolve, 调用
Student [name=Simon, age=12, gender=MALE]
true
result6

 

总结:

当进行序列化的时候:

首先JVM会先调用writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.

跟着JVM将调用writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.

当反序列化的时候:

JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.

然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).

注意到在writeReplace和readResolve,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.

 

(3)类实现Externalizable 接口 

   Externalizable 接口继承 Serializable接口: 

public interface Externalizable extends Serializable ;

Serializable接口是一个mark interface,没有实际方法;而Externalizable 接口提供了两个方法:

void  readExternal (ObjectInput in) ;

void  writeExternal (ObjectOutput out) ;

readExternal (ObjectInput in):从输入流中读取内容恢复对象;

writeExternal (ObjectOutput out) : 写入对象到输出流中;

示例:

 

 1 public class Student implements Externalizable{
 2     private static final long serialVersionUID = -6552367376730553255L;
 3     
 4     private String name;
 5     private int age;
 6     private Gender gender;
 7     
 8     public Student() {
 9         System.out.println("Student 实例化");
10     }
11     
12     @Override
13     public void writeExternal(ObjectOutput out) throws IOException {
14         out.writeObject(name);
15         out.writeInt(age);
16         out.writeObject(gender);
17         
18     }
19     @Override
20     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
21         this.name = (String)in.readObject();
22         this.age = in.readInt();
23         this.gender = (Gender)in.readObject();
24     }
25 
26 }
Externalizable
 

使用Externalizable进行序列化时,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。因此,必须提供一个无参构造器,访问权限为public;否则会抛出java.io.InvalidClassException 异常;

总结:Externalizable接口实现的功能与Serializable接口类似,Serializable序列化时不会调用默认的构造器,而Externalizable序列化时会调用默认构造器;

 

感谢:深入理解Java对象序列化 - 51CTO.COM

转载于:https://www.cnblogs.com/gscq073240/articles/6928436.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值