好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java序列化总结

Java序列化总结

Java序列化总结

 

     前一段时间写的关于集合类源码分析的博客中其实一直没有提到两个方法,那就是writeObject和readObject方法。这两个方法涉及到序列化的内容,这篇博文总结遇到过的和序列化相关的内容。

      什么是序列化?

     序列化是将对象的状态信息转化为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后可以通过存储区中读取或反序列化对象的状态重新创建对象。

      为什么要序列化?

     有两个最重要的原因促使对序列化的使用:一个原因是将对象的状态保持在存储媒体中,以便可以在以后重新创建精确的副本;另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。例如,在网络中传输的数据都必须要序列化。

      Java中的序列化

     Java中的序列化机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输或者持久化存储到数据库或文件系统中,然后再需要的时候可以读取字节流中的信息重构一个相同的对象。序列化在Java中有着广泛的应用,RMI、Hessian等技术都是以此为基础的。

     下面是一些序列化涉及到的内容的例子。

     UserInfo类是下面序列化例子中都要用到的一个保存基本信息的类。

  1   public   class  UserInfo  implements   Serializable {
   2  
  3       private   static   final   long  serialVersionUID = 1L ;
   4       public   static  String defaultPostcode = "310000" ;
   5       private   int   age;
   6       private   String name;
   7  
  8       public   int   getAge() {
   9           return   age;
  10       }
  11  
 12       public   void  setAge( int   age) {
  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   static   String getDefaultPostcode() {
  25           return   defaultPostcode;
  26       }
  27  
 28       public   static   void   setDefaultPostcode(String defaultPostcode) {
  29          UserInfo.defaultPostcode =  defaultPostcode;
  30       }
  31  
 32       public   void   desSelf() {
  33          System.out.println("Default Postcode: " +  getDefaultPostcode());
  34          System.out.println("Age: " +  getAge());
  35          System.out.println("Name: " +  getName());
  36       }
  37  }

     结合UserInfo的内容,先看下面这个main方法。

  1   public   static   void  main(String[] args)  throws   IOException, ClassNotFoundException {
   2          FileOutputStream fos =  new  FileOutputStream("temp.out" );
   3          ObjectOutputStream oos =  new   ObjectOutputStream(fos);
   4          UserInfo user =  new   UserInfo();
   5          user.setAge(25 );
   6          user.setName("Tom" );
   7          System.out.println("Before Serialize" );
   8           user.desSelf();
   9           //   保存对象后修改了DefaultPostcode 
 10          UserInfo.setDefaultPostcode("110" );
  11           oos.writeObject(user);
  12           oos.flush();
  13          FileInputStream fis =  new  FileInputStream("temp.out" );
  14          ObjectInputStream ois =  new   ObjectInputStream(fis);
  15          user =  (UserInfo)ois.readObject();
  16          System.out.println("After Deserialize" );
  17           user.desSelf();
  18      }

     在5、6两行设置了age为25,name为Tom,然后输出了UserInfo自己的描述:

Before Serialize
Default Postcode: 310000
Age: 25
Name: Tom

     可以看到Age和Name分别是设置的值。defaultPostCode是UserInfo的一个静态变量,它的值是类中指定的310000。然后将这个对象进行了序列化,保存在temp.out中。

     在第10行修改了defaultPostcode的值为110,然后反序列化并输出user的描述信息,结果如下:

After Deserialize
Default Postcode: 110
Age: 25
Name: Tom

     为什么反序列化后defaultPostcode不是310000而是修改的110呢? 因为序列化保存的是对象的状态,而静态变量属于类的状态,在序列化的时候不会被保存。

     注意,需要被序列化的对象必须实现Serializable接口,否则在序列化的时候会抛出java.io.NotSerializableException异常。

     实现Serializable接口总是会要求添加一个serialVersionUID属性,有以下两种形式:

  private static final long serialVersionUID = 5511561554099546149L;
  private static final long serialVersionUID = 1L;

     它们有什么区别呢? 一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

     下面看一个和序列化相关的关键字Transient(在 《ArrayList源码分析》 中提到过)。

      Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

      transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

    有点抽象,看个例子应该能明白。

  1   public   class  UserInfo  implements   Serializable {
   2        private   static   final   long  serialVersionUID = 996890129747019948L ;
   3        private   String name;
   4        private   transient   String psw;
   5   
  6        public   UserInfo(String name, String psw) {
   7            this .name =  name;
   8            this .psw =  psw;
   9        }
  10   
 11        public   String toString() {
  12            return  "name=" + name + ", psw=" +  psw;
  13        }
  14    }
  15   
 16   public   class   TestTransient {
  17       public   static   void   main(String[] args) {
  18          UserInfo userInfo =  new  UserInfo("张三", "123456" );
  19           System.out.println(userInfo);
  20           try   {
  21               //   序列化,被设置为transient的属性没有被序列化 
 22              ObjectOutputStream o =  new  ObjectOutputStream( new   FileOutputStream(
  23                      "UserInfo.out" ));
  24               o.writeObject(userInfo);
  25               o.close();
  26          }  catch   (Exception e) {
  27               //   TODO: handle exception 
 28               e.printStackTrace();
  29           }
  30           try   {
  31               //   重新读取内容 
 32              ObjectInputStream in =  new  ObjectInputStream( new   FileInputStream(
  33                      "UserInfo.out" ));
  34              UserInfo readUserInfo =  (UserInfo) in.readObject();
  35               //  读取后psw的内容为null 
 36               System.out.println(readUserInfo.toString());
  37          }  catch   (Exception e) {
  38               //   TODO: handle exception 
 39               e.printStackTrace();
  40           }
  41       }
  42  }

     下面说一下上文提到的ArrayList中的writeObject和readObject。先看这两个方法在ArrayList中的具体内容。

  1   private   void   writeObject(java.io.ObjectOutputStream s)
   2           throws   java.io.IOException{
   3       int  expectedModCount =  modCount;
   4       s.defaultWriteObject();
   5       s.writeInt(elementData.length);
   6       for  ( int  i=0; i<size; i++ )
   7           s.writeObject(elementData[i]);
   8  
  9       if  (modCount !=  expectedModCount) {
  10           throw   new   ConcurrentModificationException();
  11       }
  12   }
  13  
 14   private   void   readObject(java.io.ObjectInputStream s)
  15           throws   java.io.IOException, ClassNotFoundException {
  16       s.defaultReadObject();
  17       int  arrayLength =  s.readInt();
  18      Object[] a = elementData =  new   Object[arrayLength];
  19       for  ( int  i=0; i<size; i++ )
  20          a[i] =  s.readObject();
  21  }

     这两个方法都是private的且没在ArrayList中被调用过,那为什么需要这两个方法呢?

     writeObject和readObject并不是在每个类和接口中都会定义,而只是定义在哪些在序列化和反序列化过程中需要特殊处理的类中。

  stackoverflow上的解答:http://stackoverflow.com/questions/7467313/why-are-readobject-and-writeobject-private-and-why-would-i-write-transient-vari

     也就是说通过这两个方法可以自己去控制序列化和反序列化的过程。下面是这两个方法的一个例子。

  1   private   static   final   long  serialVersionUID = 1L ;
   2  
  3       private  String password = "pass" ;
   4  
  5       public   String getPassword() {
   6           return   password;
   7       }
   8  
  9       public   void   setPassword(String password) {
  10           this .password =  password;
  11       }
  12  
 13       private   void   writeObject(ObjectOutputStream out) {
  14           try   {
  15              PutField putFields =  out.putFields();
  16              System.out.println("原密码:" +  password);
  17              password = "encryption"; //  模拟加密 
 18              putFields.put("password" , password);
  19              System.out.println("加密后的密码" +  password);
  20               out.writeFields();
  21          }  catch   (IOException e) {
  22               e.printStackTrace();
  23           }
  24       }
  25  
 26       private   void   readObject(ObjectInputStream in) {
  27           try   {
  28              GetField readFields =  in.readFields();
  29              Object object = readFields.get("password", "" );
  30              System.out.println("要解密的字符串:" +  object.toString());
  31              password = "pass"; //  模拟解密,需要获得本地的密钥 
 32          }  catch   (IOException e) {
  33               e.printStackTrace();
  34          }  catch   (ClassNotFoundException e) {
  35               e.printStackTrace();
  36           }
  37  
 38       }
  39  
 40       public   static   void   main(String[] args) {
  41           try   {
  42              ObjectOutputStream out =  new   ObjectOutputStream(
  43                       new  FileOutputStream("result.obj" ));
  44              out.writeObject( new   Test());
  45               out.close();
  46  
 47              ObjectInputStream oin =  new  ObjectInputStream( new   FileInputStream(
  48                      "result.obj" ));
  49              Test t =  (Test) oin.readObject();
  50              System.out.println("解密后的字符串:" +  t.getPassword());
  51               oin.close();
  52          }  catch   (FileNotFoundException e) {
  53               e.printStackTrace();
  54          }  catch   (IOException e) {
  55               e.printStackTrace();
  56          }  catch   (ClassNotFoundException e) {
  57               e.printStackTrace();
  58           }
  59      }

     writeObject 方法中,对密码进行了加密,在 readObject 中则对 password 进行解密,只有拥有密钥的客户端,才可以正确的解析出密码,确保了数据的安全。

     最后说一下序列化的存储规则。

  1   public   class   Test {
   2       public   static   void  main(String[] args)  throws   IOException,
   3               ClassNotFoundException {
   4          FileOutputStream fos =  new  FileOutputStream("temp.out" );
   5          ObjectOutputStream oos =  new   ObjectOutputStream(fos);
   6          UserInfo user =  new   UserInfo();
   7          user.setAge(25 );
   8          user.setName("Tom" );
   9           oos.writeObject(user);
  10           oos.flush();
  11          System.out.println( new  File("temp.out" ).length());
  12           oos.writeObject(user);
  13           oos.flush();
  14           oos.close();
  15          System.out.println( new  File("temp.out" ).length());
  16  
 17          FileInputStream fis =  new  FileInputStream("temp.out" );
  18          ObjectInputStream ois =  new   ObjectInputStream(fis);
  19          UserInfo user1 =  (UserInfo) ois.readObject();
  20          UserInfo user2 =  (UserInfo) ois.readObject();
  21           ois.close();
  22          System.out.println(user1 ==  user2);
  23       }
  24  }

     为什么同一对象写入两次,文件的大小不是写入一次的文件大小的两倍呢?而第三次写入和第二次写入增加的长度是一样的呢?为什么反序列化后的两个对象比较结果是true呢?

     Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得user1和user2二者相等,输出 true。该存储规则极大的节省了存储空间。

     肯定还有我不知道的内容,望多交流讨论。 

 

 

分类:  Java基础

标签:  序列化

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Java序列化总结的详细内容...

  阅读:40次