24 Java学习之对象序列化和反序列化

Posted Hermioner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了24 Java学习之对象序列化和反序列化相关的知识,希望对你有一定的参考价值。

一. 序列化和反序列化的概念

对象序列化:把对象转换为字节序列的过程

对象反序列化:把字节序列恢复为对象的过程

1. 为何要进行序列化

        我们知道当虚拟机停止运行之后,内存中的对象就会消失。在很多应用中,需要对某些对象进行序列化,让他们离开内存空间,进入物理硬盘,便于长期保存。例如,最常见的是WEB服务器中的Session对象,当有10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些Session先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

        当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

即对象序列化主要有两种用途:

(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

(2)在网络上传送对象的字节序列。

 二. 如何序列化

1. 前提条件

如果要让每个对象支持序列化机制,比如让它的类是可序列化的,则该类必须实现如下两个接口之一:

  • Serializable
  • Extmalizable:该接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅仅实现Serializable接口的类可以采用默认的序列化方式

2. 相关API

有两个类常常用于序列化和反序列化:java.io.ObjectOutputStream和java.io.ObjectInputStream

(1)java.io.ObjectOutputStream

         java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

(2)java.io.ObjectInputStream

         java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回。

3. 对象序列化步骤

 (1)创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流;

 (2)通过对象输出流的writeObject()方法写对象

4. 对象反序列化的步骤

(1)创建一个对象输入流,它可以包装一个其它类型的源输入流,如文件输入流;

(2)通过对象输入流的readObject()方法读取对象。

5. 重要原则

  • Serializable是一个标示性接口,接口中没有定义任何的方法或字段,仅用于标示可序列化的语义。要序列化必须实现,否则异常
  • 静态变量和成员方法不可序列化。
  • 一个类要能被序列化,该类中的所有引用对象也必须是可以被序列化的。否则整个序列化操作将会失败,并且会抛出一个NotSerializableException,除非我们将不可序列化的引用标记为transient。
  • 声明成transient的变量不被序列化工具存储,同样,static变量也不被存储。

三. 使用举例

 1. 将一个对象序列化之后存储到文件中

 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Person implements Serializable{
 6     private static final long serialVersionUID = 1L;
 7     public String name;
 8     public int age;
 9 
10     public Person(String name, int age) {
11         this.name = name;
12         this.age = age;
13     }
14 
15     public String getName() {
16         return name;
17     }
18 
19     public void setName(String name) {
20         this.name = name;
21     }
22 
23     public int getAge() {
24         return age;
25     }
26 
27     public void setAge(int age) {
28         this.age = age;
29     }
30 
31 }
View Code
 1 package com.test.a;
 2 
 3 import java.io.FileNotFoundException;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 import java.io.ObjectOutputStream;
 7 
 8 
 9 public class Test {
10     public static void main(String args[]) throws FileNotFoundException, IOException{
11         Person person=new Person("zhangsan", 23);
12         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
13         ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
14         objectOutputStream.writeObject(person);
15         objectOutputStream.close();
16     }
17 }
View Code

说明:上面是执行结果。对象序列化之后,写入的是一个二进制文件,所以打开乱码是正常现象,不过透过乱码我们还是能够知道就是我们序列化的哪个对象。Person对象实现了Serializable接口,这个接口没有任何方法需要被实现,只是一个标记接口,表示这个类的对象可以被序列化,如果没有明确写了实现这个接口,就会抛出异常。

2. 从文件中反序列化对象

 1 package com.test.a;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 
11 public class Test {
12     public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{
13         Person person=new Person("zhangsan", 23);
14         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
15         ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
16         objectOutputStream.writeObject(person);
17         objectOutputStream.close();
18         
19         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path));
20         Person person2=(Person) objectInputStream.readObject();
21         System.out.println(person2.getName());
22         System.out.println(person2.getAge());
23         objectInputStream.close();
24     }
25 }
26 
27 zhangsan
28 23
View Code

说明:在调用readObject()方法的时候,有一个强转的动作。所以在反序列化时,要提供java对象所属类的class文件。

3. 多个对象的反序列化

 1 package com.test.a;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 
11 public class Test {
12     public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{
13         Person person=new Person("zhangsan", 23);
14         Person person2=new Person("lisi", 13);
15         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
16         ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
17         objectOutputStream.writeObject(person);
18         objectOutputStream.writeObject(person2);
19         objectOutputStream.close();
20         
21         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path));
22         Person person4=(Person) objectInputStream.readObject();
23         System.out.println(person4.getName());
24         System.out.println(person4.getAge());
25         Person person5=(Person) objectInputStream.readObject();
26         System.out.println(person5.getName());
27         System.out.println(person5.getAge());
28         objectInputStream.close();
29     }
30 }
31 
32 
33 zhangsan
34 23
35 lisi
36 13
View Code

说明:如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取。

4. 对象引用的序列化

 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Person implements Serializable {
 6     private static final long serialVersionUID = 1L;
 7     public String name;
 8     public int age;
 9     public Man man;
10 
11     public Person(String name, int age, Man man) {
12         this.name = name;
13         this.age = age;
14         this.man = man;
15     }
16 
17     public String getName() {
18         return name;
19     }
20 
21     public void setName(String name) {
22         this.name = name;
23     }
24 
25     public int getAge() {
26         return age;
27     }
28 
29     public void setAge(int age) {
30         this.age = age;
31     }
32 
33     public Man getMan() {
34         return man;
35     }
36 
37     public void setMan(Man man) {
38         this.man = man;
39     }
40 
41 }
View Code
 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public abstract class Man implements Serializable{  //必须实现Serializable
 6     public abstract void getInfo();
 7 }
 8 
 9 
10 package com.test.a;
11 
12 public class Femal extends Man{
13 
14     @Override
15     public void getInfo() {
16         System.out.println("Femal");
17         
18     }
19 
20 }
21 
22 package com.test.a;
23 
24 public class Male extends Man{
25 
26     @Override
27     public void getInfo() {
28         System.out.println("male");
29         
30     }
31 
32 }
View Code
 1 package com.test.a;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 
11 public class Test {
12     public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{
13         Person person=new Person("zhangsan",24, new Femal());
14         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
15         ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
16         objectOutputStream.writeObject(person);
17         objectOutputStream.close();
18         
19         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path));
20         Person person4=(Person) objectInputStream.readObject();
21         System.out.println(person4.getName());
22         System.out.println(person4.getAge());
23         Femal man=(Femal) person4.getMan();
24         man.getInfo();
25         objectInputStream.close();
26     }
27 }
View Code
1 zhangsan
2 24
3 Femal
View Code

说明:上面介绍对象的成员变量都是基本数据类型,如果对象的成员变量是引用类型,这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化。

四. serialVersionUID

serialVersionUID:字面意思是序列化的版本号,凡是实现了Seriallizable接口的类都有一个表示序列化版本标识符的静态变量:

private static final long serialVersionUID = 1L;

实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示

 

 看上面的提示信息,可以知道有两种方式来生成serial version ID

(1)采用默认方式

private static final long serialVersionUID = 1L;

这种方式生成的serialVersionUID是1L.

(2)采用第二种

private static final long serialVersionUID = -6587084022709540081L;

  这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的。

1. serialVersionUID的作用

(1)假设如下Person对象进行序列化和反序列化----没有加入serialVersionUID

 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Person implements Serializable {
 6 
 7     public String name;
 8     public int age;
 9     
10     
11     public Person(String name,int age) {
12         this.name=name;
13         this.age=age;
14     }
15 
16     public String getName() {
17         return name;
18     }
19 
20     public void setName(String name) {
21         this.name = name;
22     }
23 
24     public int getAge() {
25         return age;
26     }
27 
28     public void setAge(int age) {
29         this.age = age;
30     }
31 }
View Code
 1 package com.test.a;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 
11 public class Test {
12     public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{
13         Person person=new Person("zhangsan",24);
14         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
15         ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
16         objectOutputStream.writeObject(person);
17         objectOutputStream.close();
18         
19         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path));
20         Person person4=(Person) objectInputStream.readObject();
21         System.out.println(person4);
22         System.out.println(person4.getName());
23         System.out.println(person4.getAge());
24 
25         objectInputStream.close();
26     }
27 }
28 
29 com.test.a.Person@448139f0
30 zhangsan
31 24
View Code
 

com.test.a.Person@448139f0
zhangsan
24

(2)修改Person,添加一个新的属性

 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Person implements Serializable {
 6 
 7     public String name;
 8     public int age;
 9     public String sex;/////new added
10     
11     
12     public Person(String name,int age) {
13         this.name=name;
14         this.age=age;
15     }
16     
17     public Person(String name,int age,String sex)/////new added
18     {
19         this.name=name;
20         this.age=age;
21         this.sex=sex;
22     }
23 
24     public String getName() {
25         return name;
26     }
27 
28     public void setName(String name) {
29         this.name = name;
30     }
31 
32     public int getAge() {
33         return age;
34     }
35 
36     public void setAge(int age) {
37         this.age = age;
38     }
39 }
View Code

此时只执行反序列化,(因为生成的对象还是调用的之前两个参数的哪个对象)

 1 package com.test.a;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 
11 public class Test {
12     public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{
13 //        Person person=new Person("zhangsan",24);
14         String path="C:\\\\Users\\\\hermioner\\\\Desktop\\\\test.txt";
15 //        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path));
16 //        objectOutputStream.writeObject(person);
17 //        objectOutputStream.close();
18         
19         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path));
20         Person person4=(Person) objectInputStream.readObject();
21         System.out.println(person4);
22         System.out.println(person4.getName());
23         System.out.println(person4.getAge());
24 
25         objectInputStream.close();
26     }
27 }
View Code
1 Exception in thread "main" java.io.InvalidClassException: com.test.a.Person; local class incompatible: stream classdesc serialVersionUID = 4647091331428092166, local class serialVersionUID = -7232940355925514760
2     at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
3     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883)
4     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749)
5     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040)
6     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
7     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
8     at com.test.a.Test.main(Test.java:20)
View Code

说明:此时用的还是之前序列化的结果进行反序列化,只是此次反序列化的之前,还修改了Person。因此会造成不兼容的现象。根据上面错误提示我们知道:文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在Test例子中,没有指定Person类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显

以上是关于24 Java学习之对象序列化和反序列化的主要内容,如果未能解决你的问题,请参考以下文章

Zookeeper学习之Jute序列化以及通信协议详解

Java学习笔记——序列化和反序列化

java学习之坦克大战游戏

jackson学习之二:jackson-core

C#中如何实现多个观测数据对象序列化和反序列化?

什么是java的序列化和反序列化?