Java对象序列化和反序列化

什么是序列化与反序列化

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

在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。

概念       

序列化:把Java对象转换为字节序列的过程;
反序列化:把字节序列恢复为Java对象的过程。

对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包

JDK类库中的序列化API

java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
  只有实现了Serializable和Externalizable接口的类的对象才能被序列化。Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以 采用默认的序列化方式 。
  对象序列化包括如下步骤:
  1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
  2) 通过对象输出流的writeObject()方法写对象。
   对象反序列化的步骤如下:
  1) 创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
  2) 通过对象输入流的readObject()方法读取对象。

举个栗子说明对象序列化和反序列化:

实现serializable接口的Person类

[java]  view plain  copy
  1. import java.io.Serializable;  
  2.   
  3. /** 
  4.  * <p>description 测试对象序列化和反序列化,对象Person</p> 
  5.  * <p>date 2016年7月24日 下午9:25:51</p> 
  6.  * @author Administrator 
  7.  * @version 
  8.  * @since 
  9.  */  
  10. public class Person implements Serializable {  
  11.     /** 
  12.      * 序列化ID 
  13.      */  
  14.     private static final long serialVersionUID = -5809782578272943999L;  
  15.     private int age;  
  16.     private String name;  
  17.     private String sex;  
  18.           
  19.     public int getAge() {  
  20.         return age;  
  21.     }  
  22.     public void setAge(int age) {  
  23.         this.age = age;  
  24.     }  
  25.     public String getName() {  
  26.         return name;  
  27.     }  
  28.     public void setName(String name) {  
  29.         this.name = name;  
  30.     }  
  31.     public String getSex() {  
  32.         return sex;  
  33.     }  
  34.     public void setSex(String sex) {  
  35.         this.sex = sex;  
  36.     }  
  37. }  
序列化和反序列化Person类对象

[java]  view plain  copy
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.ObjectInputStream;  
  7. import java.io.ObjectOutputStream;  
  8. import java.text.MessageFormat;  
  9.   
  10. /** 
  11.  * @description 测试对象的序列化和反序列化 
  12.  * @author Administrator 
  13.  * @date 2016年7月24日 下午7:18:53 
  14.  * @version 
  15.  * @since 
  16.  */  
  17. public class TestObjSerializeAndDeserialize {  
  18.       
  19.     static String pathname =  "resource/Person.txt";  
  20.   
  21.     public static void main(String[] args) throws Exception {  
  22.         // TODO Auto-generated method stub  
  23.         serializePerson();  
  24.         Person p = deSerializePerson();  
  25.         String pattern = "name={0},age={1},sex={2}";  
  26.         Object[] arguments = {p.getName(),p.getAge(),p.getSex()};  
  27.         System.out.println(MessageFormat.format(pattern, arguments));  
  28.     }  
  29.       
  30.     /** 
  31.      *  
  32.      * <p>description 序列化Person对象</p> 
  33.      * <p>date 2016年7月24日 下午9:23:36</p> 
  34.      * @author Admin 
  35.      *  
  36.      * @throws FileNotFoundException 
  37.      * @throws IOException 
  38.      */  
  39.     private static void serializePerson() throws FileNotFoundException, IOException{  
  40.         Person person = new Person();  
  41.         person.setName("bonjean");  
  42.         person.setAge(25);  
  43.         person.setSex("男");  
  44.         //ObjectOutputStream对象输出流,将Person对象存储到resource目录的Person.txt文件中(pathname),完成对Person对象的序列化操作  
  45.         ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File(pathname)));  
  46.         oo.writeObject(person);  
  47.         System.out.println("Person对象序列化成功!");  
  48.         oo.close();  
  49.     }  
  50.       
  51.     /** 
  52.      *  
  53.      * <p>description 反序列化Person对象</p> 
  54.      * <p>date 2016年7月24日 下午10:23:25</p> 
  55.      * @author Admin 
  56.      *  
  57.      * @return Person 
  58.      * @throws Exception 
  59.      * @throws IOException 
  60.      */  
  61.     private static Person deSerializePerson() throws Exception, IOException{  
  62.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(pathname)));  
  63.         Person person = (Person)ois.readObject();  
  64.         System.out.println("Person对象反序列化成功!");  
  65.         return person;  
  66.     }  
  67. }  

序列化Person成功后在目录resource下生成了一个Person.txt文件,而反序列化Person是读取Person.txt后生成了一个Person对象。

再来一个栗子

[java]  view plain  copy
  1. import java.io.*;  
  2. import java.util.Date;  
  3.   
  4. public class ObjectSaver {  
  5.     public static void main(String[] args) throws Exception {  
  6.         /*其中的  resource/objectFile.obj 表示存放序列化对象的文件*/  
  7.         String pathname =  "resource/objectFile.obj";  
  8.           
  9.         //序列化对象  
  10.         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(pathname));  
  11.         Stu student = new Stu("bonjean"25);      
  12.         out.writeObject("你好!");    //写入字面值常量  
  13.         out.writeObject(new Date());    //写入匿名Date对象  
  14.         out.writeObject(student);    //写入customer对象  
  15.         out.close();  
  16.   
  17.           
  18.         //反序列化对象  
  19.         ObjectInputStream in = new ObjectInputStream(new FileInputStream(pathname));  
  20.         System.out.println("obj1 " + (String) in.readObject());    //读取字面值常量  
  21.         System.out.println("obj2 " + (Date) in.readObject());    //读取匿名Date对象  
  22.         Stu obj3 = (Stu) in.readObject();    //读取customer对象  
  23.         System.out.println("obj3 " + obj3);  
  24.         in.close();  
  25.     }  
  26. }  
  27.   
  28. class Stu implements Serializable {  
  29.     private String name;  
  30.     private int age;  
  31.     public Stu(String name, int age) {  
  32.         this.name = name;  
  33.         this.age = age;  
  34.     }  
  35.   
  36.     public String toString() {  
  37.         return "name=" + name + ", age=" + age;  
  38.     }  
  39. }  

结果:

obj1 你好!
obj2 Mon Jul 25 22:01:49 CST 2016
obj3 name=bonjean, age=25

 读取对象的顺序与写入时的顺序要一致;对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态非静态字段的值.

serialVersionUID的作用

s​e​r​i​a​l​V​e​r​s​i​o​n​U​I​D​:​ ​字​面​意​思​上​是​序​列​化​的​版​本​号​,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量
[java]  view plain  copy
  1. private static final long serialVersionUID  

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


用鼠标点击就会弹出生成serialVersionUID的对话框,如下图所示:

serialVersionUID有两种生成方式:

(1)采用这种方式生成的serialVersionUID是1L,例如:

[java]  view plain  copy
  1. private static final long serialVersionUID = 1L;  

(2)采用这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的,例如:
[java]  view plain  copy
  1. private static final long serialVersionUID = 4603642343377807741L;  
添加了之后就不会出现那个警告提示了,如下所示:


那么serialVersionUID(序列化版本号)到底有什么用呢,我们用如下的例子来说明一下serialVersionUID的作用,看下面的代码:

[java]  view plain  copy
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.ObjectInputStream;  
  7. import java.io.ObjectOutputStream;  
  8. import java.io.Serializable;  
  9.   
  10. /** 
  11.  * <p>description 序列化Customer对象</p> 
  12.  * <p>date 2016年7月25日 上午7:46:43</p> 
  13.  * @author Administrator 
  14.  * @version 
  15.  * @since 
  16.  */  
  17. public class TestSerialVersionUID {  
  18.   
  19.     static String pathname =  "resource/Customer.txt";  
  20.       
  21.     public static void main(String[] args) throws Exception{  
  22.         // TODO Auto-generated method stub  
  23.         serializeCustomer();  
  24.         Customer customer = deSerializeCustomer();  
  25.         System.out.println(customer);  
  26.     }  
  27.       
  28.     /** 
  29.      *  
  30.      * <p>description 序列化Customer对象</p> 
  31.      * <p>date 2016年7月25日 上午8:02:02</p> 
  32.      * @author Admin 
  33.      *  
  34.      * @throws FileNotFoundException 
  35.      * @throws IOException 
  36.      */  
  37.     public static void serializeCustomer() throws FileNotFoundException, IOException{  
  38.         Customer customer = new Customer("bonjean"25);  
  39.         //对象输出流  
  40.         ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File(pathname)));  
  41.         oo.writeObject(customer);  
  42.         System.out.println("Customer对象序列化成功!");  
  43.         oo.close();  
  44.     }  
  45.       
  46.     private static Customer deSerializeCustomer() throws Exception, IOException{  
  47.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File(pathname)));  
  48.         Customer customer = (Customer)ois.readObject();  
  49.         System.out.println("Customer对象反序列化成功!");  
  50.         return customer;  
  51.     }  
  52.   
  53. }  
  54.   
  55. /** 
  56.  *  
  57.  * <p>description Customer实现了Serializable接口,可以被序列化</p> 
  58.  * <p>date 2016年7月25日 上午7:51:52</p> 
  59.  * @author Administrator 
  60.  * @version 
  61.  * @since 
  62.  */  
  63. class Customer implements Serializable{  
  64.       
  65.     private String name;  
  66.     private int age;  
  67.   
  68.       
  69.     /** 
  70.      *  
  71.      * @param name 
  72.      * @param age 
  73.      */  
  74.     public Customer(String name, int age){  
  75.         this.name = name;  
  76.         this.age = age;  
  77.     }     
  78.     /** 
  79.      * 重写Object类的toString()方法 
  80.      *  
  81.      * @see java.lang.Object#toString() 
  82.      */  
  83.     @Override  
  84.     public String toString(){  
  85.         return "name="+name+",age="+age;  
  86.     }  
  87. }  

序列化和反序列化都成功了。

下面我们修改一下Customer类,添加多一个sex属性,如下:

[java]  view plain  copy
  1. class Customer implements Serializable{  
  2.     private String name;  
  3.     private int age;  
  4.       
  5.     //新增sex属性  
  6.     private String sex;  
  7.       
  8.     public Customer(String name, int age){  
  9.         this.name = name;  
  10.         this.age = age;  
  11.     }  
  12.       
  13.     public Customer (String name, int age, String sex) {  
  14.         this.name = name;  
  15.         this.age = age;  
  16.         this.sex = sex;  
  17.     }  
  18.       
  19.     /** 
  20.      * 重写Object类的toString()方法 
  21.      *  
  22.      * @see java.lang.Object#toString() 
  23.      */  
  24.     @Override  
  25.     public String toString(){  
  26.         return "name="+name+",age="+age+",sex="+sex;  
  27.     }  
然后执行反序列操作,此时就会抛出如下的异常信息:

Exception in thread "main" java.io.InvalidClassExceptio...

意思就是说,文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在TestSerialversionUID例子中,没有指定Customer类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。


  下面继续修改Customer类,给Customer指定一个serialVersionUID,修改后的代码如下:
[java]  view plain  copy
  1. class Customer implements Serializable{  
  2.     //Customer类中显式定义serialVersionUID  
  3.     private static final long serialVersionUID = -5182532647273106745L;  
  4.       
  5.     private String name;  
  6.     private int age;  
  7.       
  8.     //新增sex属性  
  9.     //private String sex;  
  10.       
  11.     public Customer(String name, int age){  
  12.         this.name = name;  
  13.         this.age = age;  
  14.     }  
  15.       
  16.     /*public Customer (String name, int age, String sex) { 
  17.         this.name = name; 
  18.         this.age = age; 
  19.         this.sex = sex; 
  20.     }*/  
  21.       
  22.     /** 
  23.      * 重写Object类的toString()方法 
  24.      *  
  25.      * @see java.lang.Object#toString() 
  26.      */  
  27.     @Override  
  28.     public String toString(){  
  29.         return "name="+name+",age="+age+",sex="+sex;  
  30.     }  
重新执行序列化操作,将Customer对象序列化到本地硬盘的Customer.txt文件存储,然后修改Customer类,添加sex属性,修改后的Customer类代码如下:
[java]  view plain  copy
  1. class Customer implements Serializable{  
  2.     //Customer类中显式定义serialVersionUID  
  3.     private static final long serialVersionUID = -5182532647273106745L;  
  4.       
  5.     private String name;  
  6.     private int age;  
  7.       
  8.     //新增sex属性  
  9.     private String sex;  
  10.   
  11.     public Customer(String name, int age){  
  12.         this.name = name;  
  13.         this.age = age;  
  14.     }  
  15.       
  16.     public Customer (String name, int age, String sex) {  
  17.         this.name = name;  
  18.         this.age = age;  
  19.         this.sex = sex;  
  20.     }  
  21.       
  22.     /** 
  23.      * 重写Object类的toString()方法 
  24.      *  
  25.      * @see java.lang.Object#toString() 
  26.      */  
  27.     @Override  
  28.     public String toString(){  
  29.         return "name="+name+",age="+age+",sex="+sex;  
  30.     }  

执行反序列操作,这次就可以反序列成功.

serialVersionUID的取值

  serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
  类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。

  显式地定义serialVersionUID有两种用途:
    1、 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
    2、 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值