对象序列化

对象序列化

对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象,对象序列化机制运行把内存中的Java对象转换成平台无关的二进制流,从而允许把这个二进制流持久保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这个二进制流,都可以将这种二进制流恢复成原来的Java对象。

对象序列化将一个Java对象写入IO流中,对象的反序列化则是从IO流中恢复原的该Java对象。

Java对象实现序列化的方式:

(1)实现Serializaable接口,该接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可以序列化的。

(2)实现Externalizable接口,


使用对象流实现序列化

通过对象流实现序列化的步骤:

(1)创建一个ObjectOutputStream输出流。

(2)调用OjbectOutputStream对象的writeObject方法输出可序列化对象。

经典实例:

public class SerializableTest {

	public static void main(String[] args) {
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream("D:\\test.txt"));
			Person p = new Person("Android", 7);
			// 将p对象写入输出流
			oos.writeObject(p);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (oos != null) {
				try {
					oos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}

class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

}

反序列化的步骤:

(1)创建一个ObjectInputStream输入流。

(2)调用ObjectInputStream对象的readObject方法读取流中的对象。该方法返回一个Object类型的java对象。

经典实例:

public static void main(String[] args) {
		ObjectInputStream ois = null;
		try {
			//创建一个ObjectInputStream输出流
			ois = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
			//从流中读取一个java对象
			Person p = (Person) ois.readObject();
			System.out.println("name=" + p.getName() + "  ,age=" + p.getAge());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}

反序列化机制无须通过构造器来初始化Java对象。如果反序列化某个子类实例时,反序列化机制需要恢复其关联的父类实例,恢复这些父类实例有两种方式:使用反序列化机制;使用父类无参数的构造函数。

如果一个可序列化类有多个父类(直接父类或间接父类),则该类的所有父类要么是可序列化的,要么有无参数的构造器,否则序列化时抛出IvaalidClassException异常。


对象引用的序列化

当程序序列化一个对象时,如果该对象持有一个其他对象的引用,为了在反序列化时可以正常恢复该对象,则程序会顺便把他引用的其他对象也进行序列化,否则该对象不能进行序列化。

Java序列化机制采用了一种特殊的序列化算法,其内容如下:

(1)所有保存到磁盘中的对象都有一个序列化编号。

(2)当程序试图序列化一个对象时,程序将先检测该对象是否已经序列化过,只有当该对象从未被序列化过,系统才会将该对象转换成字节序列并输出。

(3)如果某个对象是已经序列化过的,程序将直接只输出一个序列化编号,而不是再次重新序列化该对象。

注意:当使用Java序列化机制序列化可变对象时,一定要注意,只有当第一次调用wirteObject方法来输出对象时才会将对象转换为字节序列,并写出到ObjectOutputStream;在后面程序中,如果该对象的属性发送改变,即再次调用wirteObject方法输出该对象时,改变后的属性不会被输出。


自定义序列化

递归序列化:当对某个对象进行序列化时,系统会自动把该对象的所有属性依次进行序列化,如果某个属性引用到另一个对象,则被引用的对象也会被序列化。如果被引用的对象的属性也应用了其他对象,则被引用的对象也会被序列化。

通过在属性前加transient关键字,可以指定Java序列化时无须理会该属性,transient关键字只能用来修饰属性,不可以修饰Java程序中其他部分。比如: private  transient  int  age;

但使用transient关键字修饰属性虽然很简单方便,但被transient修饰的属性将被完全隔离在序列化机制之外,这样导致在反序列化恢复Java对象时无法获得该属性值。Java提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各属性,甚至完全不序列化某些属性。

自定义序列化需要对如下个方法进行处理

(1)private  void  writeObject(java.io.ObjectOutputStream  out) throws IOException;

(2)private  void  readObject(java.io.ObjectInputStream  in) throws IOException,ClassNotFoundException;

(3)private  void  readObjectNoData()throws ObjectStreamException;

writeObject方法负责写入特定类的实例的状态,以便相应的readObject方法可以恢复它。通过重写该方法,程序员可以完全获得对序列化机制的控制,程序员可以自主决定那些属性需要序列化,需要怎样序列化。默认情况下,该方法会调用out.defaultWriteObject来保存Java对象的各属性,从而可以实现序列化Java对象状态的目的。

readObject方法负责从流中读取并恢复对象属性,通过重写该方法,程序员可以完全获得对反序列化机制的控制,可以自主决定需要反序列化那些属性,以及进行怎样的反序列化。默认情况下,该方法会调用in.defaultReadObject来恢复Java对象的非静态属性,通常情况下readObject方法与readObject对应,如果writeObject方法中对Java对象的属性进行了一些处理,则应该在readObject方法中对该属性进行相应的反处理,以便正确恢复该对象。

当序列化流不完整时,readObjectNoData方法可以用来正确地初始化反序列化的对象。

经典实例:

public class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		// TODO Auto-generated method stub
		out.writeObject(new StringBuffer(name).reverse());
		out.writeInt(age);

	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		this.name = ((StringBuffer) in.readObject()).reverse().toString();
		this.age = in.readInt();
	}

}
上面程序中与前面说的序列化和反序列化的区别是:序列化后的对象流,即使有Cracker获取到Person对象流,他看到name将是我们加密后的name指,这样就提高了序列化的安全性。注意,writeObject方法存储属性的顺序应该和readObject方法中恢复属性的顺序一致,否则将不能正常恢复该Java对象。

还有一种更测底的自定义机制,它可以在序列化对象时将那个该对象替换成其他对象。

ANY-ACCESS-MODIFIER  Object  writeReplace() throws ObjectStreamException;

此writeReplace方法将有序列化机制调用,前提是该方法存在。

public class Person implements Serializable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	//从写wirteReplace方法,程序在序列化该对象之前,先调用该方法
	private Object wirteReplace()  throws ObjectStreamException {
		// TODO Auto-generated method stub
		ArrayList<Object> list=new ArrayList<Object>();
		list.add(name);
		list.add(age);
		return list;
	}

}
我们知道系统在序列化某个对象之前,会先调用该对象的如下两个方法:writeReplace和writeObject,系统总是先调用序列化对象的writeReplace方法,如果该方法返回另一个对象,系统将再次调用另一个对象的writeReplace方法.........直到该方法不再返回另一个对象为止,最后程序调用该对象的writeObject方法来保存该对象的状态。

与writeReplace方法相对的是,序列化机制还有一个特殊的方法,他可以实现保护性负责整个对象。

ANY-ACCESS-MODIFIER  Object   readResolve() throws ObjectStreamException;

这个方法会紧接着readObject方法之后调用,该方法的返回值将会替代原来反序列化的对象,而原来readObject反序列化的对象将会被立即丢弃。


Externalizable自定义序列化机制

Java还提供了另一种序列化机制,这种序列化方式完全由程序员决定存储和恢复对象数据,要实现该目的,java类必须实现Externalizable接口。

Externalizable接口定义的两个方法:

(1)void   readExternal(ObjectInput  in):需要序列化的类实现readExternal方法来实现反序列化。该方法调用DataInput(它是ObjectInput的父接口)的方法来恢复基本类似的属性值,调用ObjectInput的readObject方法来恢复引用类型的属性值。

(2)void   writeExternal(ObjectOutput  out):需要序列化的类实现writeExternal方法来保存对象的状态,该方法调用DataInput(它是ObjectInput的父接口)的方法来保存基本类型的属性值,,调用ObjectOutput的writeObject方法来保存引用类型的属性值。

经典实例:

public class Person implements Externalizable {

	private String name;
	private int age;

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(new StringBuffer(name).reverse());
		out.writeInt(age);
	}
	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		// TODO Auto-generated method stub
		this.name = ((StringBuffer) in.readObject()).reverse().toString();
		this.age = in.readInt();
	}
}


两种序列化机制的对比:


两种序列化机制的对比
实现Serializable接口 实现Externalizable接口
系统自动存储必要的信息 程序员决定存储那些信息
Java内建支持,易于实现,只需实现该接口即可 仅仅提供两个空方法,实现该接口必须为两个空方法提供实现
性能略差 性能略高

总结:虽然实现Externalizable节诶可能带来一定的性能提升,由于实现Externalizable接口导致了编程度的增加,所以大部分还是采用实现Serializable接口来实现序列化。

对象序列化注意事项:

(1)对象的类名、属性(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、static属性(即静态属性)、transient属性都不会被序列化。

(2)实现Serializable接口的类如果需要想让某个属性不被序列化,可以在该属性前加transient修饰符,而不是加static关键字,虽然能达到效果,但static不能这样使用。

(3)保证序列化对象的属性的类型是可以序列化的,否则需要使用transient关键字来修饰该属性,要不然,则该类是不可序列化的。

(4)反序列化对象时必须有序列化对象的class文件。

(5)当通过文件、网络来读取实例化后的对象时,必须按实际写入的顺序读取。


版本

反序列化Java对象时必须提供该对象的class文件,如果项目升级,系统的class文件同样也会升级。为了避免升级后和升级前的兼容性。Java序列化机制允许序列化类提供一个private static final 的serialVersionUID属性值,该属性值用于标识该Java类的序列化版本,也就是说,如果升级后和升级前的serialVersionUID保持不变,序列化机制也会把它们当成同一个序列化版本。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章