Java学习手册:Java序列化

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请务必注明出处: https://blog.csdn.net/MaybeForever/article/details/88937504

Java提供了两种对象持久化的方式,分别为序列化和外部序列化。

一、序列化(Serialization)

在分布式的环境下,当进行远程通信时,无论是何种类型的数据,都会以二进制序列的形式在网络上传送。序列化是一种将对象以一连串的字节描述的过程,用于解决在对对象流进行读写操作时所引发的问题。序列化可以将对象的状态写在流里进行网络传输,或者保存到文件、数据库等系统里,并在需要时把该流读取出来重新构造一个相同的对象。

如何实现序列化
其实,所有要实现序列化的类都必须实现Serializable接口,Serializable接口位于java.lang包中,它里面没有包含任何方法。使用一个输出流来构造一个ObjectOutputStream对象,然后使用该对象的writeObject(Object obj)方法就可以将obj对象写出(即保存其状态),要恢复时可以使用其对应的输入流。

序列化有以下两个特点
①如果一个类能被序列化,那么它的子类也能够被序列化。
②由于static(静态)代表类的成员,transient(Java语言关键字,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。)代表对象的临时数据,因此被声明为这两种类型的数据成员是不能够被序列化的。

下面给出一个序列化的具体实例:

package com.haobi;

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

public class People implements Serializable{
	private String name;
	private int age;
	public People() {
		this.name = "haobi";
		this.age = 18;
	}
	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;
	}
	public static void main(String[] args) {
		People p = new People();
		ObjectOutputStream oos = null;
		ObjectInputStream ois = null;
		try {
			FileOutputStream fos = new FileOutputStream("perple.out");
			oos = new ObjectOutputStream(fos);
			oos.writeObject(p);
			oos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		People p1;
		try {
			FileInputStream fis = new FileInputStream("perple.out");
			ois = new ObjectInputStream(fis);
			p1 = (People)ois.readObject();
			System.out.println("name:"+p1.getName());
			System.out.println("age:"+p1.getAge());
			ois.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
//程序输出结果如下:
name:haobi
age:18

由于序列化的使用会影响系统的性能,因此如果不是必须要使用序列化,应尽可能不要使用序列化。那么在什么情况下需要使用序列化呢?
(1)需要通过网络来发送对象,或对象的状态需要被持久化到数据库或文件中。
(2)序列化能实现深复制,即可以复制引用的对象。

与序列化相对的是反序列化,它将流转换为对象。在序列化与反序列化的过程中,serialVersionUID起着非常重要的作用,每个类都有一个特定的serialVersionUID,在反序列化的过程中,通过serialVersionUID来判定类的兼容性。如果待序列化的对象与目标对象的serialVersionUID不同,那么在反序列化时就会抛出InvalidClassException异常。因此,最好在被序列化的类中显示地声明serialVersionUID(该字段必须定义为static final)。

自定义serialVersionUID主要有如下3个优点:
(1)提高程序地运行效率。如果在类中未显式声明serialVersionUID,那么在序列化时会通过计算得到一个serialVersionUID值。通过显示声明serialVersionUID的方式省去了计算的过程,因此提高了程序的运行效率。
(2)提高程序在不同平台上的兼容性。由于各个平台的编译器在计算serialVersionUID时完全有可能会采用不同的计算方式,着就会导致在一个平台上序列化的对象在另一个平台上将无法实现序列化的操作。通过显式声明serialVersionUID的方法完全可以避免该问题的发生。
(3)增强程序各个版本的可兼容性。在默认情况下,每个类都有唯一的serialVersionUID,因此,当后期对类进行修改时(例如加入新的属性),类的serialVersionUID值将会发生变化,这将会导致类在修改前对象序列化的文件在修改后将无法进行反序列化操作。同样,通过显示声明serialVersionUID也会解决这个问题。

二、外部序列化

Java语言还提供了另外一种方式来实现对象持久化,即外部序列化。其接口如下:

public interface Externalizable extends Serializable{
	void readExternal(ObjectInput in);?
	void writeExternal(ObjectOutpur out);?
}

外部序列化与序列化的主要区别在于序列化是内置的API,只需要实现Serializable接口,开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Externalizable接口中的读写方法必须由开发人员来实现。因此与实现Serializable接口的方法相比,使用Externalizable编写程序的难度更大,但是由于把控制权交给了开发人员,在编写时有更多的灵活性,对需要持久化的那些属性可以进行控制,可能会提高性能。

三、在用接口Serializable实现序列化时,这个类中的所有属性都会被序列化,那么怎样才能实现只序列化部分属性呢?

答:一种方法为实现Externalizable接口,开发人员可以根据实际需求来实现readExternal与writeExternal方法来控制序列化与反序列化所使用的属性,这种方法的缺点是增加了编程的难度。另一种方法为使用关键字transient来控制序列化的属性。被transient修饰的属性是临时的,不会被序列化。因此,可以通过把不需要被序列化的属性用transient来修饰。

四、transient

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。

猜你喜欢

转载自blog.csdn.net/MaybeForever/article/details/88937504