java中的序列化与反序列化

序列化和反序列化是通过ObjectInputStream和ObjectOutputStream的readObject()和writeObject()实现的,序列化的过程是一个对象流状态保存的过程,这里什么叫对象流,可以理解为一系列的对象,因为本身一个对象的内部的字段都是一个个对象,实际上是通过“级联”的方式,保存跟此对象所有关联的对象的状态,实际上保存了跟此对象有关系的一张“对象网”。

反序列化是还原对象状态的过程,这种还原的过程可能在同一个应用中,可能在不同的应用中,可能在不同的主机上,还原的过程不是读出原来对象的字段值然后调用构造函数重新new一个对象,而是“直接地”反序列化为一个Object对象,并没有调用该类的构造函数,jvm也没有加载该类到方法区,还原后的对象若在不同的主机,想通过反射获得更多改对象的描述信息,必须保证JVM能在本地类路径或者因特网的其他什么地方找到相关的.class文件。

序列化也可以自己控制,使用Externalizable接口,此接口继承自Serializable接口,和Serializable不同的是,使用Externalizable接口,在恢复对象的时候是调用的该类的无参构造方法,若无参构造方法不是public的,在恢复对象的时候会抛出异常,下面的代码节选自Think in java

package externalizable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Blip1 implements Externalizable {
	//public的构造方法,在回复对象的时候被调用。
	public Blip1() {
		System.out.println("Blip1 Constructor");
	}

	//在writeObject()方法的时候,会调用此方法
	public void writeExternal(ObjectOutput out) throws IOException {
		System.out.println("Blip1.writeExternal");
	}

	//在readObject()方法的时候,会调用此方法
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		System.out.println("Blip1.readExternal");
	}

}

package externalizable;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class Blip2 implements Externalizable {
	//此构造方法不是public的
	Blip2() {
		System.out.println("Blip2 Constructor");
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		System.out.println("Blip2.writeExternal");
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		System.out.println("Blip2.readExternal");
	}
}

package externalizable;

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

public class Blips {
	public static void main(String[] args) {
		System.out.println("Constructing objects:");
		Blip1 b1 = new Blip1();
		Blip2 b2 = new Blip2();
		try {
			ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
					"Blips.out"));
			System.out.println("Saving objects:");
			o.writeObject(b1);
			o.writeObject(b2);
			o.close();
			// Now get them back:
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					"Blips.out"));
			System.out.println("Recovering b1:");
			b1 = (Blip1) in.readObject();
			// OOPS! Throws an exception:
			// !System.out.println("Recovering b2:");
			// !b2 = (Blip2) in.readObject();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

输入的结果为:

Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal

下面代码演示保存和恢复一个Externalizable对象必须做的全部事情。

package externalizable;

import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Blip3 implements Externalizable {
	int i;
	String s; // No initialization

	public Blip3() {
		System.out.println("Blip3 Constructor");
		// s, i not initialized
	}

	public Blip3(String x, int a) {
		System.out.println("Blip3(String x, int a)");
		s = x;
		i = a;
		// s & i initialized only in non-default
		// constructor.
	}

	public String toString() {
		return s + i;
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		System.out.println("Blip3.writeExternal");
		// You must do this:
		out.writeObject(s);
		out.writeInt(i);
	}

	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		System.out.println("Blip3.readExternal");
		// You must do this:
		// 此顺序必须和前面相同
		s = (String) in.readObject();
		i = in.readInt();
	}

	public static void main(String[] args) {
		System.out.println("Constructing objects:");
		Blip3 b3 = new Blip3("A String ", 47);
		System.out.println(b3.toString());
		try {
			ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
					"Blip3.out"));
			System.out.println("Saving object:");
			o.writeObject(b3);
			o.close();
			// Now get it back:
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(
					"Blip3.out"));
			System.out.println("Recovering b3:");
			b3 = (Blip3) in.readObject();
			System.out.println(b3.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

输出结果:

Constructing objects:
Blip3(String x, int a)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47

控制序列化的过程的另一个途径是在一个类中定义自己的writeObject和readObject方法,方法原型如下:

private void writeObject(ObjectOutputStream oos){.....}

private void readObject(ObjectInputStream ois){.......}

在序列化一个对象时,该对象的类中某字段使用transient关键字,则该字段不会被序列化。

使一个类的某字段不被序列化还有其他的方法。若有两个类A和B,A继承了B,并实现了Serializable接口,但是父类B没有实现Serializable接口,这个时候在反序列化A对象的时候,若要访问父类B中的字段,会是什么值呢?是序列化A类对象时,对应父类字段的值么?不是!由于B类没有实现Serializable接口,所以序列化A类对象的时候不会序列化父类B中对应的字段,在反序列化A类对象的时候,当然也不会得到原来对象对应父类B中字段的值,实际上,这个时候会调用B类的无参构造函数先构造B对象,所以对应B类中对应字段的值会是无参构造函数中设置的默认值,若没有设置,引用类型为null,布尔类型为false,int、doule等为0。

所以若想在一个类中不想序列化某些字段,可以把这个字段放到另一个类(A)中,然后去继承这个类(A),这个类(A)所有子类中的对应字段都不会被序列化。

类的静态字段不会被序列化。

引起反序列化失败的可能原因有哪些?

1.在反序列化的一端jvm是否允许反序列化。

2.反序列化一端是否有对应的类

3.反序列化一端对应类的UID是否一致

序列化使用eclipse自动生成的 serialVersionUID有什么作用?

eclipse中serialVersionUID可以有两种生成策略,一种是默认的1L,另一中是生成一串长整型的值,这种方法生成的值是根据类的元信息(如类名、接口名、成员方法及属性等)生成64位的hash值。

在序列化中serialVersionUID的作用是控制类的版本变化,以保持一定的兼容。考虑这样的一个场景,客户端通过一个类的对象跟服务器端交互,若这个对象由客户端创建,对于服务器端来说,不太好控制这个对象可以调用的方法已取得服务器端的服务。一种好的思路是,服务器端序列化该类的对象实例传到客户端,客户端反序列化该对象,通过该对象调用类的方法和服务器端交互,这样服务器端可以控制客户端取得的服务。若该类的实现在服务器端被改变,若改变后的类实现和之前是不兼容的,我们可以手动的改变类中serialVersionUID的值,如将1L改为2L,这样将迫使客户端必须要升级改类的版本,否则将无法取得服务器端的服务。

对于自动生成的serialVersionUID,只要类的元信息有一点点变化(如多一个字段,字段名变化),自动生成的serialVersionUID值就会不同,但是郁闷的是当类改变的时候,eclipse不会提示你serialVersionUID已经变得陈旧,你还是得手动改原来的serialVersionUID,如删除原来的serialVersionUID,然后从新生成新的serialVersionUID,所以从这点上看,自动生成的serialVersionUID没有任何特别之处。还不如使用默认的1L那样好,看上去舒服点,呵呵。

对于同一个对象,两次序列化到一个文件中,不会使该文件的长度变为写一次的两倍,实际上写第二次的时候,没有再次把该对象的所有属性值以及一些其他的信息再写一遍,而只是写入一个引用。从文件中读出这两个对象是==为true的,实际上是同一个对象的两个不同引用。

因为对于同一个对象,第二次写只会保留前一次写的引用,所以若在一次写后改变对象的状态(改变属性值),然后再次写入同一个文件,这时候不会将改变状态后的对象再写一遍,而只是写前一个对象的引用,所以从文件中读出来的对象还是第一次写时对象的状态的值。

测试代码:

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

public class Test implements Serializable {

	private static final long serialVersionUID = 1L;
	int i;

	public static void main(String[] args) {
		Test test = new Test();
		test.i = 1;
		try {
			ObjectOutputStream oos = new ObjectOutputStream(
					new FileOutputStream(new File("out.txt")));
			oos.writeObject(test);

			// 改变对象test的状态
			test.i = 2;
			oos.writeObject(test);

			ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
					new File("out.txt")));
			Test test2 = (Test) ois.readObject();
			Test test3 = (Test) ois.readObject();

			System.out.println(test2.i); // 1
			System.out.println(test3.i); // 1
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

输出结果:


参考文章:

http://blog.csdn.net/tangjian0921/article/details/6966416

http://www.oschina.net/question/12_17367

http://blog.csdn.net/morethinkmoretry/article/details/5929345

http://zengxiankang2011.blog.163.com/blog/static/1783603192011594938588/?fromdm&isFromSearchEngine=yes

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值