java 序列化

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 对象输出到一个流,并存入一个文件:

public class PersonalData implements Serializable {
public int id
public int yearOfBirth;
public float yearlySalary;
}
PersonalData mine = new PersonalData(101, 1956, 46500.00);
FileOutputStream outstream = new FileOutputStream("PersonalData.ser");
ObjectOutputStream out = new ObjectOutputStream(outstream);
out.writeObject("My personal data"); //将一个串写入流
out.writeObject(mine); //将这个对象写入流
out.close(); // 清空并关闭流
...



  一个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 来保存的。



package com.itm.test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Simulator {

	/*****************************************************
	 * 
	 * 
	 我们可以看到,读取到的对象与保存的对象状态一样。这里有几点需要说明一下:
		1、        基本类型 的数据可以直接序列化
		2、        对象要被序列化,它的类必须要实现Serializable接口;如果一个类中有引用类型的实例变量,这个引用类型也要实现Serializable接口。
			比如上面 的例子中,Student类中有一个Book类型 的实例就是,要想让Student的对象成功序列化,那么Book也必须要实现Serializable接口。
		3、        我们看这个语句:
				ObjectOutputStreamout  = newObjectOutputStream(new FileOutputStream("seria"));
 
				我们知道 FileOutputStream类有一个带有两个参数的重载Constructor——FileOutputStream(String,boolean),
					其第二个参数如果为true且String代表的文件存在,那么将把新的内容写到原来文件的末尾而非重写这个文件,
					这里我们不能用这个版本的构造函数,
					也就是说我们必须重写这个文件,否则在读取这个文件反序列化的过程中就会抛出异常。
						导致只有我们第一次写到这个文件中的对象可以被反序列化,之后程序就会出错。
 
 
			下面的问题是如果 我们上面 用到的Book类没有实现Serializable接口,但是我们还想序列化Student类的对象 ,怎么办。
			Java为我们提供了transient这个关键字。
				如果一个变量被声明成transient,那么 在序列化的过程 中,这个变量是会被无视的。我们还是通过对上面的代码进行小的修改来说明 这个问题。
				新的Book类不实现Serializable接口
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new Simulator().go();

	}
	
	private void go(){
		Student student = new Student(new Book(2011),"小海");
		
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria"));
			out.writeObject(student); // 向seria文件中 写入。
			System.out.println("将要写入内容");
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria"));
			Student studentRead = (Student) in.readObject();
			System.out.println("Object 在这里 读取~~");
			System.out.println(studentRead);
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}


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


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


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


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

package com.itm.test;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Student implements Serializable{

	private transient Book book;
	
	private String name;
	
	public Student(Book book,String name){
		super();
		this.book = book;
		this.name = name;
	}

	public Book getBook() {
		return book;
	}

	public void setBook(Book book) {
		this.book = book;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	
	//下面这两个方法就是那组特殊的私有方法,它们会在序列化、反序列化的过程 中被自动调用   
    //我们必须保证方法的签名和下面的两个方法完全相同
	
	// (1)序列化。
	private void writeObject(ObjectOutputStream out){
		try {
			//这个方法会把这当前中非静态变量和非transient变量写到流中   
//因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中  
			out.defaultWriteObject();
			//在这里我们就把name写到了流中。
			out.writeInt(book.getIsbn());  //ObjectOutputStream中提供了写基本类型数据的方法
			//out.close();//注意,这句千万不能有,否刚将直接导致写入失败 
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	// (2)反序列化。
	private void readObject(ObjectInputStream in){
		try {
			//和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取  
            //非静态变量和非transient变量  
			in.defaultReadObject();
			
			int isbn = in.readInt(); //用这个方法来读取一个int型值,这里我们是读取书号  。
			
			book = new Book(isbn);	//这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象 
			// in.close(); 注意:也不可以要。
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	
	
	public String toString(){
		return "Student -------> book =" +book+ ",name=" +name+ "--> ";
	}
	
}
正如预料 的一样,我们成功了。一定要注意写入和读取的顺序一定要对应。像上面如果你是先写基本类型数据的话,那在读取的时候也一定要先读取基本类型的数据,这个原因我想大家都清楚,文件是有position的。


第2个问题:

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

第3个问题:

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


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

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

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


序列化






  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值