`
RepublicW
  • 浏览: 83233 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

序列化

 
阅读更多


  首先,我们来讨论一下什么是序列化以及序列化的原理;然后给出一个简单的示例来演示序列化和反序列化;有时有些信息是不应该被序列化的,我们应该如何控制;我们如何去自定义序列化内容;最后我们讨论一下在继承结构的场景中,序列化需要注意哪些内容。

  序列化概述
  序列化,简单来讲,就是以“流”的方式来保存对象,至于保存的目标地址,可以是文件,可以是数据库,也可以是网络,即通过网络将对象从一个节点传递到另一个节点。

  我们知道在Java的I/O结构中,有ObjectOutputStream和ObjectInputStream,它们可以实现将对象输出为二进制流,并从二进制流中获取对象,那为什么还需要序列化呢?这需要从Java变量的存储结构谈起,我们知道对Java来说,基础类型存储在栈上,复杂类型(引用类型)存储在堆中,对于基础类型来说,上述的操作时可行的,但对复杂类型来说,上述操作过程中,可能会产生重复的对象,造成错误。

  而序列化的工作流程如下:

  1)通过输出流保存的对象都有一个唯一的序列号。

  2)当一个对象需要保存时,先对其序列号进行检查。

  3)当保存的对象中已包含该序列号时,不需要再次保存,否则,进入正常保存的流程。

  正是通过序列号的机制,序列化才可以完整准确的保存对象的各个状态。

  序列化保存的是对象中的各个属性的值,而不是方法或者方法签名之类的信息。对于方法或者方法签名,只要JVM能够找到正确的ClassLoader,那么就可以invoke方法。

  序列化不会保存类的静态变量,因为静态变量是作用于类型,而序列化作用于对象。

  简单的序列化示例
  序列化的完整过程包括两部分:

  1)使用ObjectOutputStream将对象保存为二进制流,这一步叫做“序列化”。

  2)使用ObjectInputStream将二进制流转换成对象,这一步叫做“反序列化”。

  下面我们来演示一个简单的示例,首先定义一个Person对象,它包含name和age两个信息。

定义Person对象
  然后是两个公共方法,用来完成读、写对象的操作:


1 private static void writeObject(Object obj, String filePath)
 2 {
 3     try
 4     {
 5         FileOutputStream fos = new FileOutputStream(filePath);
 6         ObjectOutputStream os = new ObjectOutputStream(fos);
 7         os.writeObject(obj);
 8         os.flush();
 9         fos.flush();
10         os.close();
11         fos.close();
12         System.out.println("序列化成功。");
13     }
14     catch(Exception ex)
15     {
16         ex.printStackTrace();
17     }
18 }
19 
20 private static Object readObject(String filePath)
21 {
22     try
23     {
24         FileInputStream fis = new FileInputStream(filePath);
25         ObjectInputStream is = new ObjectInputStream(fis);
26         
27         Object temp = is.readObject();
28         
29         fis.close();
        is.close();
         
         if (temp != null)
         {
            System.out.println("反序列化成功。");
             return temp;
        }
     }
     catch(Exception ex)
     {
        ex.printStackTrace();
     }
   
    return null;
 }


  这里,我们将对象保存的二进制流输出到磁盘文件中。

  接下来,我们首先来看“序列化”的方法:


1 private static void serializeTest1()
2 {
3     Person person = new Person();
4     person.setName("Zhang San");
5     person.setAge(30);
6     System.out.println(person);
7     writeObject(person, "d:\\temp\\test\\person.obj");
8 }

  我们定义了一个Person实例,然后将其保存到d:\temp\test\person.obj中。

  最后,是“反序列化”的方法:


1 private static void deserializeTest1()
2 {   
3     Person temp = (Person)readObject("d:\\temp\\test\\person.obj");
4    
5     if (temp != null)
6     {
7         System.out.println(temp);
8     }
9 }

  它从d:\temp\test\person.obj中读取对象,然后进行输出。

  上述两个方法的执行结果如下:

Name:Zhang San; Age:30
序列化成功。
反序列化成功。
Name:Zhang San; Age:30
  可以看出,读取的对象和保存的对象是完全一致的。

  隐藏非序列化信息
  有时,我们的业务对象中会包含很多属性,而有些属性是比较隐私的,例如年龄、银行卡号等,这些信息是不太适合进行序列化的,特别是在需要通过网络来传输对象信息时,这些敏感信息很容易被窃取。

  Java使用transient关键字来处理这种情况,针对那些敏感的属性,我们只需使用该关键字进行修饰,那么在序列化时,对应的属性值就不会被保存。

  我们还是看一个实例,这次我们定义一个新的Person2,其中age信息是我们不希望序列化的:

定义Person2对象
  注意age的声明语句:

1 private transient int age;
  下面是“序列化”和“反序列化”的方法:


1 private static void serializeTest2()
2 {
3     Person2 person = new Person2();
4     person.setName("Zhang San");
5     person.setAge(30);
6     System.out.println(person);
7     writeObject(person, "d:\\temp\\test\\person2.obj");
8 }
9
10 private static void deserializeTest2()
11 {   
12     Person2 temp = (Person2)readObject("d:\\temp\\test\\person2.obj");
13    
14     if (temp != null)
15     {
16         System.out.println(temp);
17     }
18 }

  它的输出结果如下:

Name:Zhang San; Age:30
序列化成功。
反序列化成功。
Name:Zhang San; Age:0
  可以看到经过反序列化的对象,age的信息变成了Integer的默认值0。

  自定义序列化过程
  我们可以对序列化的过程进行定制,进行更细粒度的控制。

  思路是在业务模型中添加readObject和writeObject方法。下面看一个实例,我们新建一个类型,叫Person3:


1 class Person3 implements Serializable
2 {
3     private String name;
4     private transient int age;
5     public void setName(String name) {
6         this.name = name;
7     }
8     public String getName() {
9         return name;
10     }
11     public void setAge(int age) {
12         this.age = age;
13     }
14     public int getAge() {
15         return age;
16     }
17    
18     public String toString()
19     {
20         return "Name:" + name + "; Age:" + age;
21     }
22    
23     private void writeObject(ObjectOutputStream os)
24     {
25         try
26         {
27             os.defaultWriteObject();
28             os.writeObject(this.age);
29             System.out.println(this);
30             System.out.println("序列化成功。");
31         }
32         catch(Exception ex)
33         {
34             ex.printStackTrace();
35         }
36     }
37    
38     private void readObject(ObjectInputStream is)
39     {
40         try
41         {
42             is.defaultReadObject();
43             this.setAge(((Integer)is.readObject()).intValue() - 1);
44             System.out.println("反序列化成功。");
45             System.out.println(this);
46         }
47         catch(Exception ex)
48         {
49             ex.printStackTrace();
50         }
51     }
52 }

  请注意观察readObject和writeObject方法,它们都是private的,接受的参数是ObjectStream,然后在方法体内调用了defaultReadObject或者defaultWriteObject方法。

  这里age同样是transient的,但是在保存对象的过程中,我们单独对其进行了保存,在读取时,我们将age信息读取出来,并进行了减1处理。

  下面是测试方法:


1 private static void serializeTest3()
2 {
3     Person3 person = new Person3();
4     person.setName("Zhang San");
5     person.setAge(30);
6     System.out.println(person);
7     try
8     {
9         FileOutputStream fos = new FileOutputStream("d:\\temp\\test\\person3.obj");
10         ObjectOutputStream os = new ObjectOutputStream(fos);
11         os.writeObject(person);
12         fos.close();
13         os.close();
14     }
15     catch(Exception ex)
16     {
17         ex.printStackTrace();
18     }
19 }
20
21 private static void deserializeTest3()
22 {   
23     try
24     {
25         FileInputStream fis = new FileInputStream("d:\\temp\\test\\person3.obj");
26         ObjectInputStream is = new ObjectInputStream(fis);
27         is.readObject();
28         fis.close();
29         is.close();
30     }
31     catch(Exception ex)
32     {
33         ex.printStackTrace();
34     }
35 }

  输出结果如下:

Name:Zhang San; Age:30
序列化成功。
反序列化成功。
Name:Zhang San; Age:29
  可以看到,经过反序列化得到的对象,其age属性已经减1。

  探讨serialVersionUID
  在上文中,我们描述序列化原理时,曾经提及每个对象都会有一个唯一的序列号,这个序列号,就是serialVersionUID。

  当我们的对象实现Serializable接口时,该接口可以为我们生成serialVersionUID。

  有两种方式来生成serialVersionUID,一种是固定值:1L,一种是经过JVM计算,不同的JVM采取的计算算法可能不同。

  下面就是两个serialVersionUID的示例:

1 private static final long serialVersionUID = 1L;
2 private static final long serialVersionUID = -2380764581294638541L;
  第一行是采用固定值生成的;第二行是JVM经过计算得出的。

  那么serialVersionUID还有其他用途吗?

  我们可以使用它来控制版本兼容。如果采用JVM生成的方式,我们可以看到,当我们业务对象的代码保持不变时,多次生成的serialVersionUID也是不变的,当我们对属性进行修改时,重新生成的serialVersionUID会发生变化,当我们对方法进行修改时,serialVersionUID不变。这也从另一个侧面说明,序列化是作用于对象属性上的。

  当我们先定义了业务对象,然后对其示例进行了“序列化”,这时根据业务需求,我们修改了业务对象,那么之前“序列化”后的内容还能经过“反序列化”返回到系统中吗?这取决于业务对象是否定义了serialVersionUID,如果定义了,那么是可以返回的,如果没有定义,会抛出异常。

  来看下面的示例,定义新的类型Person4:


1 class Person4 implements Serializable
2 {
3     private String name;
4     private int age;
5     public void setName(String name) {
6         this.name = name;
7     }
8     public String getName() {
9         return name;
10     }
11     public void setAge(int age) {
12         this.age = age;
13     }
14     public int getAge() {
15         return age;
16     }
17     private void xxx(){}
18    
19     public String toString()
20     {
21         return "Name:" + name + "; Age:" + age;
22     }
23 }

  然后运行下面的方法:


1 private static void serializeTest4()
2 {
3     Person4 person = new Person4();
4     person.setName("Zhang San");
5     person.setAge(30);
6    
7     writeObject(person, "d:\\temp\\test\\person4.obj");
8 }

  接下来修改Person4,追加address属性:


1 class Person4 implements Serializable
2 {
3     private String name;
4     private int age;
5     private String address;
6     public void setName(String name) {
7         this.name = name;
8     }
9     public String getName() {
10         return name;
11     }
12     public void setAge(int age) {
13         this.age = age;
14     }
15     public int getAge() {
16         return age;
17     }
18     private void xxx(){}
19    
20     public String toString()
21     {
22         return "Name:" + name + "; Age:" + age;
23     }
24     public void setAddress(String address) {
25         this.address = address;
26     }
27     public String getAddress() {
28         return address;
29     }
30 }

  然后运行“反序列化”方法:


1 private static void deserializeTest4()
2 {   
3     Person4 temp = (Person4)readObject("d:\\temp\\test\\person4.obj");
4    
5     if (temp != null)
6     {
7         System.out.println(temp);
8     }
9 }

  可以看到,运行结果如下:


java.io.InvalidClassException: sample.serialization.Person4; local class incompatible: stream classdesc serialVersionUID = -2380764581294638541, local class serialVersionUID = -473458100724786987
    at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
    at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
    at java.io.ObjectInputStream.readClassDesc(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at sample.serialization.Sample.readObject(Sample.java:158)
    at sample.serialization.Sample.deserializeTest4(Sample.java:105)
    at sample.serialization.Sample.main(Sample.java:16)

  但是当我们在Person4中添加serialVersionUID后,再次执行上述各步骤,得出的运行结果如下:

反序列化成功。
Name:Zhang San; Age:30
  有继承结构的序列化
  业务对象会产生继承,这在管理系统中是经常看到的,如果我们有下面的业务对象:


1 class Person5
2 {
3     private String name;
4     private int age;
5     public void setName(String name) {
6         this.name = name;
7     }
8     public String getName() {
9         return name;
10     }
11     public void setAge(int age) {
12         this.age = age;
13     }
14     public int getAge() {
15         return age;
16     }
17    
18     public String toString()
19     {
20         return "Name:" + name + "; Age:" + age;
21     }
22    
23     public Person5(String name, int age)
24     {
25         this.name = name;
26         this.age = age;
27     }
28 }
29
30 class Employee extends Person5 implements Serializable
31 {
32     public Employee(String name, int age) {
33         super(name, age);
34     }
35
36     private String companyName;
37
38     public void setCompanyName(String companyName) {
39         this.companyName = companyName;
40     }
41
42     public String getCompanyName() {
43         return companyName;
44     }
45    
46     public String toString()
47     {
48         return "Name:" + super.getName() + "; Age:" + super.getAge() + "; Company:" + this.companyName;
49     }
50 }

  Employee继承在Person5,Employee实现了Serializable接口,Person5没有实现,那么运行下面的方法:


1 private static void serializeTest5()
2 {
3     Employee emp = new Employee("Zhang San", 30);
4     emp.setCompanyName("XXX");
5    
6     writeObject(emp, "d:\\temp\\test\\employee.obj");
7 }
8
9 private static void deserializeTest5()
10 {   
11     Employee temp = (Employee)readObject("d:\\temp\\test\\employee.obj");
12    
13     if (temp != null)
14     {
15         System.out.println(temp);
16     }
17 }

  会正常运行吗?事实上不会,它会抛出如下异常:


java.io.InvalidClassException: sample.serialization.Employee; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source)
    at java.io.ObjectStreamClass.checkDeserialize(Unknown Source)
    at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at sample.serialization.Sample.readObject(Sample.java:158)
    at sample.serialization.Sample.deserializeTest5(Sample.java:123)
    at sample.serialization.Sample.main(Sample.java:18)

  原因:在有继承层次的业务对象,进行序列化时,如果父类没有实现Serializable接口,那么父类必须提供默认构造函数。

  我们为Person5添加如下默认构造函数:

1 public Person5()
2 {
3     this.name = "Test";
4     this.age = 1;
5 }
  再次运行上述代码,结果如下:

Name:Zhang San; Age:30; Company:XXX
序列化成功。
反序列化成功。
Name:Test; Age:1; Company:XXX
  可以看到,反序列化后的结果,父类中的属性,已经被父类构造函数中的赋值代替了!

  因此,我们推荐在有继承层次的业务对象进行序列化时,父类也应该实现Serializable接口。我们对Person5进行修改,使其实现Serializable接口,执行结果如下:

Name:Zhang San; Age:30; Company:XXX
序列化成功。
反序列化成功。
Name:Zhang San; Age:30; Company:XXX
  这正是我们期望的结果。
分享到:
评论

相关推荐

    ASPNET中JSON的序列化和反序列化的方法

    在***中处理JSON的序列化和反序列化是Web开发中常见的需求。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,由于其简洁性和易于阅读性,被广泛用于服务器与客户端之间的数据传输。下面将详细介绍**...

    C++ JSON 序列化与反序列化

    本篇文章将深入探讨C++中JSON的序列化与反序列化。 **一、JSON序列化** 序列化是指将C++的对象转换为JSON字符串的过程,以便在网络上传输或保存到文件中。常见的C++ JSON序列化库有RapidJSON、nlohmann/json、...

    java 对象的序列化与反序列化

    Java对象的序列化和反序列化是Java编程中一项重要的技术,主要用于将对象的状态转换为字节流,以便存储或在网络上传输。这一过程对于理解Java的IO操作、持久化数据以及实现分布式通信等场景非常关键。 首先,我们来...

    XML序列化与反序列化 实战

    XML序列化与反序列化是.NET框架中处理数据交换的重要技术,它允许我们将对象的状态转换为XML格式的数据,也可以将XML数据恢复为等效的对象。这个实战项目专注于使用C#实现这一过程,使得开发者能够方便地在XML文件和...

    基于boost的序列化与反序列化

    在编程领域,序列化和反序列化是两个关键的概念,它们用于将对象的状态转换为可存储或可传输的格式,然后在需要时恢复为原始对象。Boost库提供了一个强大的工具——Boost.Serialization,来帮助程序员实现这个功能。...

    C#和Java的序列化反序列化

    在编程领域,序列化和反序列化是两个关键的概念,特别是在跨平台通信、持久化存储以及数据传输中扮演着重要角色。本篇文章将深入探讨C#和Java中的序列化与反序列化机制。 首先,我们要了解什么是序列化。序列化是指...

    序列化和反序列化的封装类

    在编程领域,序列化和反序列化是两个关键的概念,特别是在数据存储、网络传输和对象持久化等场景中。它们允许我们将对象的状态转换为字节流(序列化),然后在需要的时候将字节流还原为原来的对象(反序列化)。在C#...

    序列化和反序列化dll文件和proto

    在IT领域,序列化和反序列化是两个关键的概念,特别是在数据存储、网络通信和分布式系统中。它们涉及将对象的状态转换为可持久化的格式(序列化),以及将这种格式还原回原来的对象(反序列化)。本文将深入探讨这两...

    java序列化(Serializable)的作用和反序列化.doc

    ### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...

    jackson json序列化,反序列化所需jar包

    Jackson是Java领域中广泛使用的JSON处理库,它提供了高效的JSON序列化和反序列化功能。在Java应用程序中,我们经常需要将Java对象转换为JSON字符串(序列化)或从JSON字符串恢复Java对象(反序列化),Jackson库就是...

    C#序列化与反序列化(包括复杂xml对象)

    在C#编程中,序列化和反序列化是两个关键的概念,它们主要用于对象状态的持久化和恢复。本文将深入探讨C#中的XML序列化和反序列化技术,包括如何将XML文档解析为对象,以及如何将对象转换回XML文档进行存储。我们将...

    hessian学习基础篇——序列化和反序列化

    在IT领域,序列化和反序列化是两个关键的概念,特别是在网络通信、数据持久化以及对象存储中。本文将深入探讨Hessian框架的基础知识,它是一个高效的二进制序列化协议,广泛应用于Java和.NET之间跨语言通信。通过...

    Unity 中使用Protobuf进行序列化和反序列化的Demo

    在Unity游戏引擎中,开发人员经常需要处理数据的序列化和反序列化,以便于存储、传输或网络通信。Protocol Buffers(Protobuf)是一种高效、跨平台的数据序列化协议,由Google开发。本Demo将展示如何在Unity中集成并...

    Json反序列化

    反序列化是将JSON格式的字符串转换为程序可直接使用的对象的过程,这对于处理网络请求返回的数据至关重要。在本篇文章中,我们将深入探讨JSON反序列化的概念、原因以及在实际开发中的应用。 一、什么是JSON反序列化...

    protobuf序列化和反序列化技术

    protobuf序列化和反序列化技术是大数据处理领域中不可或缺的一部分,尤其在实时大数据场景下,高效的数据传输和存储对性能有着直接影响。谷歌推出的Protocol Buffers(简称protobuf)是一种语言无关、平台无关的数据...

    java反序列化工具

    Java反序列化是一种将已序列化的对象状态转换回对象的过程,它是Java平台中持久化数据的一种常见方式。在Java应用程序中,序列化用于保存对象的状态以便稍后恢复,或者在网络间传输对象。然而,这个过程也可能引入...

    C#对象序列化反序列化保存与读取和对象直接保存与读取

    本主题将深入探讨如何在Windows Forms(WF)环境中,利用C#语言进行对象的直接保存与读取,以及通过对象序列化和反序列化的方式实现对象的保存与恢复。 首先,我们来理解什么是对象的直接保存与读取。在C#中,可以...

    C#的控件使用:树形图TreeView如何实现序列化和反序列化.rar

    本教程将详细讲解如何在C#中实现`TreeView`控件的序列化和反序列化,这对于数据持久化和用户界面状态的保存至关重要。 首先,让我们了解序列化和反序列化的基本概念。**序列化**是将对象的状态转换为可存储或传输的...

    序列化窗体所有控件

    在编程领域,序列化是一种将对象的状态转换为可存储或可传输的形式的过程。这通常用于持久化数据,即保存对象的状态以便稍后恢复,或者在网络间传输对象。在这个主题“序列化窗体所有控件”中,我们主要关注的是如何...

    C# 数组与序列化

    在C#编程语言中,数组和序列化是两个非常重要的概念。数组是数据结构的基础,序列化则是数据传输和持久化的关键技术。接下来,我们将深入探讨这两个主题。 **数组** 数组是C#中用于存储固定数量同类型元素的集合。...

Global site tag (gtag.js) - Google Analytics