序列化与反序列化

序列化与反序列化的好处
 我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

      当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列

     我们可以通过序列化来保存一个对象的状态(实例变量)到文件中,也可以从这个格式化的文件中很容易地读取对象的状态从而可以恢复我们保存的对象。用来实现序列化的类都在java.io包中,我们常用的类或接口有:ObjectOutputStream:提供序列化对象并把其写入流的方法,ObjectInputStream:读取流并反序列化对象Serializable:一个对象想要被序列化,那么它的类就要实现此接口,下面我们先通过一个简单的例子演示一起序列化/反序列化的过程:

1)JDK类库中序列化的步骤
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\serialiseObj.txt”));
步骤二:通过对象输出流的writeObject()方法写对象:
out.writeObject(“Hello”);
out.writeObject(new Date());
2)JDK类库中反序列化的步骤
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\serialiseObj.txt”));
步骤二:通过对象输出流的readObject()方法读取对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();


1、串行化的原理:

Java 串行化技术可以使你将一个对象的状态写入一个Byte 里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象。这种机制允许你将对象通过网络进行传播,并可以随时把对象持久化到数据库、文件等系统里。Java的串行化机制是RMI、EJB等技术的技术基础。用途:利用对象的串行化实现保存应用程序的当前工作状态,下次再启动的时候将自动地恢复到上次执行的状态。

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

序列化的实现:将需要被序列化的类实现Serializable接口,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

2、串行化的特点:

    (1)如果某个类能够被串行化,其子类也可以被串行化。如果该类有父类,则分两种情况来考虑,如果该父类已经实现了可串行化接口。则其父类的相应字段及属性的处理和该类相同;如果该类的父类没有实现可串行化接口,则该类的父类所有的字段属性将不会串行化。

  (2)声明为statictransient类型的成员数据不能被串行化。因为static代表类的状态, transient代表对象的临时数据;

  (3)如果一个类中有引用类型的实例变量(成员属性),这个引用类型也要实现Serializable接口。

  (4)相关的类和接口:在java.io包中提供的涉及对象的串行化的类与接口有ObjectOutput接口、ObjectOutputStream类、ObjectInput接口、ObjectInputStream类。

    (1)ObjectOutput接口:它继承DataOutput接口并且支持对象的串行化,其内的writeObject()方法实现存储一个对象。ObjectInput接口:它继承DataInput接口并且支持对象的串行化,其内的readObject()方法实现读取一个对象。

    (2)ObjectOutputStream类:它继承OutputStream并且实现ObjectOutput接口。利用该类来实现将对象存储(调用ObjectOutput接口中的writeObject()方法)。ObjectInputStream类:它继承InputStream类并且实现ObjectInput接口。利用该类来实现读取一个对象(调用ObjectInput接口中的readObject()方法)。

  对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数),否则编译的时候就会报错。在反串行化的时候,默认构造函数被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。

writeObject和readObject本身就是线程安全的,传输过程中是不允许被并发访问的。所以对象能一个一个接连不断的传过来。

下面模拟测试成员属性实例变量未实现序列化,如何改写序列化方法来实现对象存储。

package kevin.seria;

public class Book{
	private int isbn;
	
	public Book(int isbn) {
		super();
		this.isbn = isbn;
	}

	public int getIsbn() {
		return isbn;
	}

	public void setIsbn(int isbn) {
		this.isbn = isbn;
	}

	@Override
	public String toString() {
		return "Book [isbn=" + isbn + "]";
	}
	
}
      已知一个类student对象被成功的序列化了,而其成员属性实例对象没有实现序列化接口,而序列化过程中跳过了Book实例,所以当恢复对象状态的时候 ,它被赋予了默认值null,这也就意味着我们不能使用它。那如果Book类没有实现Serializable接口,如何实现对它的状态进行保存?

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

package kevin.seria;

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;
	}
	
	//下面这两个方法就是那组特殊的私有方法,它们会在序列化、反序列化的过程 中被自动调用 
	//我们必须保证方法的签名和下面的两个方法完全相同
	
	//这个方法会在序列化的过程中被调用 
	private void writeObject(ObjectOutputStream out){
		try {
			out.defaultWriteObject(); //这个方法会把这当前中非静态变量和非transient变量写到流中
									  //在这里我们就把name写到了流中。
			//因为我们要保存Book的状态,所以我们还要想办法把其状态写入流中
			out.writeInt(book.getIsbn());//ObjectOutputStream中提供了写基本类型数据的方法
			//out.close();//注意,这句千万不能有,否刚将直接导致写入失败
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
	
	//这个方法会在反序列化的过程中被调用
	private void readObject(ObjectInputStream in){
		try {
			in.defaultReadObject(); //和defaultWriteObject()方法相对应,默认的反序列化方法,会从流中读取
									//非静态变量和非transient变量
			int isbn  = in.readInt(); //用这个方法来读取一个int型值,这里我们是读取书号
			book  = new Book(isbn); //这里我们就通过读取的 保存的状态构造 了一个和原来一样的Book对象
			//in.close();同样的这句也不能有
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public String toString() {
		return "Student [book=" + book + ", name=" + name + "]";
	}

}

输出结果是:

object has been written..
object read here:
Student [book=Book [isbn=2011], name=kevin]

最后发现book对象的结构保存成功了,也证明了这2个方法在序列化或者反序列化的过程中被调用,就是一定要注意写入和读取的顺序要对应。

参考文章1; 参考文章2; 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值