码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2018-10-18 22:11:11      阅读:183      评论:0      收藏:0      [点我收藏+]

标签:摘要算法   数据类型   服务   ret   session   zhang   lis   它的   二进制   

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

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

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

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就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。

因此总结Class不兼容解决办法:

  • 添加serialVersionUID,重复(1)和(2)的操做的化就可以避免Class不兼容现象了。
  • 还有中办法:可以在类修改以后,再重新序列化,这样反序列化就可以拿到最新的序列化的文件拉。

显式地定义serialVersionUID有两种用途:

  •     在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
  •     在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。

SerialVersionUID其实是JAVA的序列化机制采取的一种特殊的算法:

  • 所有保存到磁盘中的对象都有一个序列化编号。
  • 当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化,系统才会将该对象转换成字节序列并输出。
  • 如果对象已经被序列化,程序将直接输出一个序列化编号,而不是重新序列化。

五. 自定义序列化

 1. transient关键字

  用transient关键字来修饰实例变量,该变量就会被完全隔离在序列化机制之外。

技术分享图片
 1 package com.test.a;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Person implements Serializable {
 6     private static final long serialVersionUID = -7232940355925514760L;
 7     public String name;
 8     public transient 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 }
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 com.test.a.Person@448139f0
2 zhangsan
3 0
View Code

说明:age的打印值为0,是默认初始化值。如果是字符串,就为null。这说明使用tranisent修饰的变量,在经过序列化和反序列化之后,JAVA对象会丢失该实例变量的值。因此,Java提供了一种自定义序列化机制,这样程序就可以自己来控制如何序列化各实例变量,甚至不序列化实例变量。

2. 自定义序列化

在序列化和反序列化过程中需要特殊处理的类应该提供如下的方法,这些方法用于实现自定义的序列化。

writeObject()

readObject()

这两个方法并不属于任何的类和接口,只要在要序列化的类中提供这两个方法,就会在序列化机制中自动被调用。

其中writeObject方法用于写入特定类的实例状态,以便相应的readObject方法可以恢复它。通过重写该方法,程序员可以获取对序列化的控制,可以自主决定可以哪些实例变量需要序列化,怎样序列化。该方法调用out.defaultWriteObject来保存JAVA对象的实例变量,从而可以实现序列化java对象状态的目的。

技术分享图片
 1 package com.test.a;
 2 import java.io.IOException;
 3 import java.io.ObjectInputStream;
 4 import java.io.ObjectOutputStream;
 5 import java.io.Serializable;
 6 
 7 public class Person implements Serializable
 8 {
 9     private static final long serialVersionUID = 1L;
10     int age;
11     String address;
12     double height;
13     public Person(int age, String address, double height)
14     {
15         this.age = age;
16         this.address = address;
17         this.height = height;
18     }
19     
20     //JAVA BEAN自定义的writeObject方法
21     private void writeObject(ObjectOutputStream out) throws IOException
22     {
23         System.out.println("writeObejct ------");
24         out.writeInt(age);
25         out.writeObject(new StringBuffer(address).reverse());
26         out.writeDouble(height);
27     }
28     
29     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
30     {
31         System.out.println("readObject ------");
32         this.age = in.readInt();
33         this.address = ((StringBuffer)in.readObject()).reverse().toString();
34         this.height = in.readDouble();
35     }
36 }
View Code
技术分享图片
 1 package com.test.a;
 2 import java.io.FileInputStream;
 3 import java.io.FileOutputStream;
 4 import java.io.IOException;
 5 import java.io.ObjectInputStream;
 6 import java.io.ObjectOutputStream;
 7 
 8 public class Test
 9 {
10     public static void main(String[] args) throws IOException, IOException,
11             ClassNotFoundException
12     {
13         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
14                 "C:\\Users\\hermioner\\Desktop\\test.txt"));
15         Person p = new Person(25, "China", 180);
16         oos.writeObject(p);
17         oos.close();
18 
19         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
20                 "C:\\Users\\hermioner\\Desktop\\test.txt"));
21         Person p1 = (Person) ois.readObject();
22         System.out.println("age=" + p1.age  + ";address=" + p1.address
23                 + ";height=" + p1.height);
24         ois.close();
25     }
26 }
View Code
技术分享图片
1 writeObejct ------
2 readObject ------
3 age=25;address=China;height=180.0
View Code

1、这个地方跟前面的区别就是在Person类中提供了writeObject方法和readObject方法,并且提供了具体的实现。

2、在ObjectOutputStream调用writeObject方法执行过程,肯定调用了Person类的writeObject方法

3、Person中自定义的writeObject和readObject只能是private才可以被调用,如果是public则不会被调用。???WHY

4. 自定义序列API

   比如ArrayList和HashMap都自定义了序列化和反序列化中的核心方法

 

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

标签:摘要算法   数据类型   服务   ret   session   zhang   lis   它的   二进制   

原文地址:https://www.cnblogs.com/Hermioner/p/9776434.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!