java 序列化

来源:互联网 发布:sql server2008破解版 编辑:程序博客网 时间:2024/06/06 19:41
1、序列化是干什么的? 
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保 存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。


2、什么情况下需要序列化
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;

3、当对一个对象实现序列化时,究竟发生了什么?
在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量(instance ariable)比如:

java 代码
Foo myFoo = new Foo();
myFoo .setWidth(37);
myFoo.setHeight(70);

当 通过下面的代码序列化之后,MyFoo对象中的width和Height实例变量的值(37,70)都被保存到foo.ser文件中,这样以后又可以把它 从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对 象。

java 代码

FileOutputStream fs = new FileOutputStream("foo.ser");
ObjectOutputStream os = new ObjectOutputStream(fs);
os.writeObject(myFoo);
4、实现序列化(保存到一个文件)的步骤 
a、Make a FileOutputStream
java 代码
FileOutputStream fs = new FileOutputStream(“foo.ser”);
b、Make a ObjectOutputStream

java 代码

ObjectOutputStream os = new ObjectOutputStream(fs);
c、write the object

java 代码

os.writeObject(myObject1);
os.writeObject(myObject2);
os.writeObject(myObject3);
d、 close the ObjectOutputStream

java 代码

os.close();

===========================================

Bean Serializable Interface 的接口让 BEAN 可以串行化,将其变成一个可保存为以后使用的二进制流。当一个BEAN被系列化到磁盘上或者其他任何地方,其状态被保存起来,其中的属性值也不会改变。在BEAN的规范中,JSP并没有要求BEAN实现Serializable接口。但是,如果您希望自己控制您所创建的组件的serialization进程,或者您想serialize并不是标准组件扩展的组件,您必须了解serialization and deserialization的细节。

  有几个原因你会把 BEAN 冷藏起来以备后用。      有些服务器通过将所有的SESSION 数据(包括BEAN)写入磁盘来支持任意长的SESSION生命期,即使服务器停机也不会丢失。当服务器重新启动后,串行化的数据被恢复。同样的理由,在重负载的站点上支持服务器分簇的环境中,许多服务器通过串行化来复制SESSION。如果你的BEAN不支持串行化,服务器就不能正确地保存和传输类。

  通过同样的策略,你可以选择将BEAN保存在磁盘上或者数据库中,以备后用。例如,也许可以将客户的购物车实现为一个BEAN,在访问期间将其保存在数据库中。


  如果BEAN需要特殊的复杂的初始设置,可以将BEAN设置好后串行化保存在磁盘上。这个BEAN的“快照”可以用在任何需要的地方,包括在$#@60;jsp:useBean$#@62;中用beanName属性的调用。

  $#@60;jsp:useBean$#@62;标签中的beanName属性,用来实例化一个串行化的BEAN,而不是用来从一个类创建一个全新的实例。如果BEAN还没有创建,beanName属性传给java.beans.Bean.instantiate()方法,由类装载器对类进行实例化。它首先假定存在一个串行化的BEAN(带有扩展名.ser),然后会将其激活。如果这个操作失败,它就会实例化一个新的实例。




  下面简单介绍一下这个接口

  对象能包含其它的对象,而这其它的对象又可以包含另外的对象。JAVA serialization能够自动的处理嵌套的对象。对于一个对象的简单的域,writeObject()直接将值写入流。而,当遇到一个对象域时,writeObject()被再次调用,如果这个对象内嵌另一个对象,那么,writeObject() 又被调用,直到对象能被直接写入流为止。程序员所需要做的是将对象传入ObjectOutputStream 的writeObject() 方法,剩下的将又系统自动完成。下面的例子创建了一个调用mine对象的PersonalData对象。代码实现的是将一个串和mine 对象输出到一个流,并存入一个文件:

[java] view plaincopyprint?
  1. public class PersonalData implements Serializable {  
  2. public int id  
  3. public int yearOfBirth;  
  4. public float yearlySalary;  
  5. }  
  6. PersonalData mine = new PersonalData(101195646500.00);  
  7. FileOutputStream outstream = new FileOutputStream("PersonalData.ser");  
  8. ObjectOutputStream out = new ObjectOutputStream(outstream);  
  9. out.writeObject("My personal data"); //将一个串写入流  
  10. out.writeObject(mine); //将这个对象写入流  
  11. out.close(); // 清空并关闭流  
  12. ...  



  一个FileOutputStream对象被创建且传到一个ObjectOutputStream。当out.writeObject() 被调用,这个串和mine 对象被objects are serializ顺序加入一个存入文件PersonalData.ser的字节对列。

  您应该注意上述类是实现的java.io.Serializable接口。因为它并未指定要实现的方法,所以Serializable被称为"tagging interface" ,但是它仅仅"tags"它自己的对象是一个特殊的类型。任一个您希望serialize的对象都应该实现这个接口。这是必须的。否则,用到流技术时将根本不工作。例如,如果您试着去serialize 一个没有实现这个接口的对象,一个 NotSerializableException将产生。

 

     类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

  Java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成一组byte,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。

  
要想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject( )方法把对象写入OutputStream了。

  
writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

  读的时候,你得把InputStream嵌到ObjectInputStream里面,然后再调用readObject( )方法。不过这样读出来的,只是一个Object的reference,因此在用之前,还得先下传。readObject 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。

   defaultReadObject 方法使用  流中的信息来分配流中通过当前对象中相应命名字段   保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。



[java] view plaincopyprint?
  1. package com.itm.test;  
  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. public class Simulator {  
  11.   
  12.     /***************************************************** 
  13.      *  
  14.      *  
  15.      我们可以看到,读取到的对象与保存的对象状态一样。这里有几点需要说明一下: 
  16.         1、        基本类型 的数据可以直接序列化 
  17.         2、        对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。 
  18.             比如上面 的例子中,Student类中有一个Book类型 的实例就是,要想让Student的对象成功序列化,那么Book也必须要实现Serializable接口。 
  19.         3、        我们看这个语句: 
  20.                 ObjectOutputStreamout  = newObjectOutputStream(new FileOutputStream("seria")); 
  21.   
  22.                 我们知道 FileOutputStream类有一个带有两个参数的重载Constructor——FileOutputStream(String,boolean), 
  23.                     其第二个参数如果为true且String代表的文件存在,那么将把新的内容写到原来文件的末尾而非重写这个文件, 
  24.                     这里我们不能用这个版本的构造函数, 
  25.                     也就是说我们必须重写这个文件,否则在读取这个文件反序列化的过程中就会抛出异常。 
  26.                         导致只有我们第一次写到这个文件中的对象可以被反序列化,之后程序就会出错。 
  27.   
  28.   
  29.             下面的问题是如果 我们上面 用到的Book类没有实现Serializable接口,但是我们还想序列化Student类的对象 ,怎么办。 
  30.             Java为我们提供了transient这个关键字。 
  31.                 如果一个变量被声明成transient,那么 在序列化的过程 中,这个变量是会被无视的。我们还是通过对上面的代码进行小的修改来说明 这个问题。 
  32.                 新的Book类不实现Serializable接口 
  33.      *  
  34.      * @param args 
  35.      */  
  36.     public static void main(String[] args) {  
  37.         new Simulator().go();  
  38.   
  39.     }  
  40.       
  41.     private void go(){  
  42.         Student student = new Student(new Book(2011),"小海");  
  43.           
  44.         try {  
  45.             ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));  
  46.             out.writeObject(student); // 向seria文件中 写入。  
  47.             System.out.println("将要写入内容");  
  48.             out.close();  
  49.         } catch (FileNotFoundException e) {  
  50.             e.printStackTrace();  
  51.         } catch (IOException e) {  
  52.             e.printStackTrace();  
  53.         }  
  54.           
  55.         try {  
  56.             ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));  
  57.             Student studentRead = (Student) in.readObject();  
  58.             System.out.println("Object 在这里 读取~~");  
  59.             System.out.println(studentRead);  
  60.               
  61.         } catch (FileNotFoundException e) {  
  62.             // TODO Auto-generated catch block  
  63.             e.printStackTrace();  
  64.         } catch (IOException e) {  
  65.             // TODO Auto-generated catch block  
  66.             e.printStackTrace();  
  67.         } catch (ClassNotFoundException e) {  
  68.             // TODO Auto-generated catch block  
  69.             e.printStackTrace();  
  70.         }  
  71.     }  
  72.   
  73. }  


(2), 因为我们要序列化Student类的对象,所以我们必须实现Serializable接口,然而Book是Student的一个实例变量,它的类没有实现Serializable接口,所以为了顺序完成序列化,我们把这个实例变量声明为transient以在序列化的过程中跳过它。


=======================================


     可以看到,student对象被成功的序列化了。因为序列化过程中跳过了Book实例,所以当恢复对象状态的时候 ,它被赋予了默认值null,这也就意味着我们不能使用它。那如果Book类没有实现Serializable接口,但我们还想对它的状态进行保存,这可以实现 吗?答案是肯定的。


       Java针对这种情况有一种特殊的机制—— 一组私有(回调)方法(这个我们马上在代码中看到),可以在要被序列化的类中实现它们,在序列化和的序列化的过程中它们会被自动调用。所以在这组方法中我们可以调用ObjectOutputStream/ObjectInputStream的一些有用方法来实现对象状态的保存。

[java] view plaincopyprint?
  1. package com.itm.test;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.ObjectInputStream;  
  5. import java.io.ObjectOutputStream;  
  6. import java.io.Serializable;  
  7.   
  8. public class Student implements Serializable{  
  9.   
  10.     private transient Book book;  
  11.       
  12.     private String name;  
  13.       
  14.     public Student(Book book,String name){  
  15.         super();  
  16.         this.book = book;  
  17.         this.name = name;  
  18.     }  
  19.   
  20.     public Book getBook() {  
  21.         return book;  
  22.     }  
  23.   
  24.     public void setBook(Book book) {  
  25.         this.book = book;  
  26.     }  
  27.   
  28.     public String getName() {  
  29.         return name;  
  30.     }  
  31.   
  32.     public void setName(String name) {  
  33.         this.name = name;  
  34.     }  
  35.       
  36.       
  37.     //下面这两个方法就是那组特殊的私有方法,它们会在序列化、反序列化的过程 中被自动调用     
  38.     //我们必须保证方法的签名和下面的两个方法完全相同  
  39.       
  40.     // (1)序列化。  
  41.     private void writeObject(ObjectOutputStream out){  
  42.         try {  
  43.             //这个方法会把这当前中非静态变量和非transient变量写到流中     
  44. //因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中    
  45.             <span style="color:#ff0000;">out.defaultWriteObject();</span>  
  46.             //在这里我们就把name写到了流中。  
  47.             out.writeInt(book.getIsbn());  //ObjectOutputStream中提供了写基本类型数据的方法  
  48.             //out.close();//注意,这句千万不能有,否刚将直接导致写入失败   
  49.         } catch (IOException e) {  
  50.             // TODO Auto-generated catch block  
  51.             e.printStackTrace();  
  52.         }  
  53.     }  
  54.       
  55.       
  56.     // (2)反序列化。  
  57.     private void readObject(ObjectInputStream in){  
  58.         try {  
  59.             //和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取    
  60.             //非静态变量和非transient变量    
  61.             <span style="color:#cc0000;">in.defaultReadObject();  
  62.               
  63.             int isbn = in.readInt(); </span>//用这个方法来读取一个int型值,这里我们是读取书号  。  
  64.               
  65.             book = new Book(isbn);  //这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象   
  66.             // in.close(); 注意:也不可以要。  
  67.         } catch (IOException e) {  
  68.             // TODO Auto-generated catch block  
  69.             e.printStackTrace();  
  70.         } catch (ClassNotFoundException e) {  
  71.             // TODO Auto-generated catch block  
  72.             e.printStackTrace();  
  73.         }  
  74.     }  
  75.       
  76.       
  77.       
  78.       
  79.     public String toString(){  
  80.         return "Student -------> book =" +book+ ",name=" +name+ "--> ";  
  81.     }  
  82.       
  83. }  
正如预料 的一样,我们成功了。一定要注意写入和读取的顺序一定要对应。像上面如果你是先写基本类型数据的话,那在读取的时候也一定要先读取基本类型的数据,这个原因我想大家都清楚,文件是有position的。


第2个问题:

Object是每个类的超类,但是它没有实现 Serializable接口,但是我们照样在序列化对象,所以说明一个类要序列化,它的父类不一定要实现Serializable接口。但是在父类中定义 的状态能被正确 的保存以及读取吗?

第3个问题:

如果将一个对象写入某文件(比如是a),那么之后对这个对象进行一些修改,然后把修改的对象再写入文件a,那么文件a中会包含该对象的两个 版本吗?


事与愿违啊,它输出了三个kevin,这证明 我们对student名字的修改并没有被写入。原因是序列化输出过程跟踪写入流的对象,试图将同一个对象写入流时,不会导致该对象被复制,而只是将一个句柄写入流,该句柄指向流中相同对象的第一个对象出现的位置。

那我们如何来避免这种情况 ,让它输出三个人名呢,方法是在writeObject()之前调用out.reset()方法,这个方法的作用是清除流中保存的写入对象的记录。

请见:http://blog.csdn.net/moreevan/article/details/6698529  这位仁兄。


原创粉丝点击