} } }

    Java回顾之序列化

    添加时间:2013-5-9 点击量:

      第一篇:Java回顾之I/O


      第二篇:Java回顾之收集通信


      第三篇:Java回顾之多线程


      第四篇:Java回顾之多线程同步


      第五篇:Java回顾之凑集



      在这篇文章里,我们存眷对象序列化。


      起首,我们来评论辩论一下什么是序列化以及序列化的道理;然后给出一个简单的示例来演示序列化和反序列化;有时有些信息是不该该被序列化的,我们应当如何把握;我们如何去自定义序列化内容;最后我们评论辩论一下在持续布局的场景中,序列化须要重视哪些内容。


      序列化概述


      序列化,简单来讲,就是以“流”的体式格式来保存对象,至于保存的目标地址,可所以文件,可所以数据库,也可所以收集,即经由过程收集将对象从一个节点传递到另一个节点。


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


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


      1)经由过程输出流保存的对象都有一个独一的序列号。


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


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


      恰是经由过程序列号的机制,序列化才可以完全正确的保存对象的各个状况。


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


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


      简单的序列化示例


      序列化的完全过程包含两项目组:


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


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


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


    定义Person对象

     1 class Person 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
    18 public String toString()
    19 {
    20 return Name: + name + ; Age: + age;
    21 }
    22 }



      然后是两个公共办法,用来完成读、写对象的操纵:



     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();
    30 is.close();
    31
    32 if (temp != null
    33 {
    34 System.out.println(反序列化成功。);
    35 return temp;
    36 }
    37 }
    38 catch(Exception ex)
    39 {
    40 ex.printStackTrace();
    41 }
    42
    43 return null;
    44 }


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


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



    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对象

     1 class Person2 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 }



      重视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


      这恰是我们期望的成果。

    容易发怒的意思就是: 别人做了蠢事, 然后我们代替他们, 表现出笨蛋的样子。—— 蔡康永
    分享到: