了解java的序列化与反序列化

序列化是一种对象持久化的手段,普遍应用在网络传输、RMI等场景中。Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即:这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意:对象序列化保存的是对象的”状态”,即它的成员变量,对象序列化不会关注类中的静态变量。

1. 如何序列化和反序列化?

一个Java类必须实现java.io.Serializable接口,这个类的对象才可以被序列化。
package aty;

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

public class Student implements Serializable {

	private int id;

	private String name;

	private int age;

	private boolean sex;

	public Student(int id, String name, int age, boolean sex) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.sex = sex;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", age=" + age
				+ ", sex=" + sex + "]";
	}

}

之后我们可以通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import aty.Student;

public class Utils {

	public static void main(String[] args) throws Exception {
		String file = "c:/stu.obj";
		
		Student stu = new Student(1, "aty", 27, true);
		
		serialize(file, stu);
		
		Object o1 = deserialize(file);
		
		// Student [id=1, name=aty, age=27, sex=true]
		System.out.println(o1);
	}

	public static void serialize(String filePath, Object object)
			throws IOException {
		FileOutputStream fout = new FileOutputStream(filePath);
		ObjectOutputStream oos = new ObjectOutputStream(fout);
		oos.writeObject(object);
		oos.close();
	}

	public static Object deserialize(String filePath) throws Exception {
		FileInputStream fis = new FileInputStream(filePath);
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object result = ois.readObject();
		ois.close();
		return result;
	}
}

2. serialVersionUID作用是什么?

上面的Student实现了Serializable接口,但是没有添加serialVersionUID,在eclipse下会给出警告:
The serializable class Student does not declare a static final serialVersionUID field of type long

解决这个警告很简单:让eclipse自动帮我们添加一个serialVersionUID。


// 默认的serialVersionUID
private static final long serialVersionUID = 1L;
// eclipse按照一定算法生产的serialVersionUID
private static final long serialVersionUID = 6990027678698020472L;

我们平常一般都是用第一种方式来解决这个警告的。那serialVersionUID作用是什么呢?我们知道:如果我们将aty.Student这个类的对象序列号到c:/stu.obj文件中,那么当我们反序列化的时候,如果classpath没有这个类会报错:


可以看到:在序列化和反序列化的时候, 类的全路径必须完全一致。现在我们做一个这样的实验:
1.设置serialVersionUID=1后,将student对象序列化到文件中
2.将serialVersionUID修改成2后,从文件中反序列化student对象。

那么我们会遇到下面的异常:


也就是说:在序列化和反序列化的过程中,类不仅路径要完全一致,serialVersionUID也必需相同。这就是这个字段的用处。

3. transient关键字的作用是什么?

我们知道可以使用transient关键字来修饰类的成员变量:
private transient int age;

java序列化的时候不会保存类的静态字段的值,如果一个属性有transient关键字,那么这个属性的值会在序列化的时候被忽略。transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

4. 反序列化与构造函数

在进行反序列化的时候,并没有调用类的构造方法,而是直接根据他们的序列化数据在内存中创建新的对象。java中创建一个对象并不一定非要调用构造函数,比如反序列化的时候,比如用Unsafe创建对象的时候。

5. 序列化与继承

如果A类继承了B类,如果A和B都实现了Serializable接口,那么序列化A对象的时候会自动将其父类中的字段也进行序列化。如果A实现了Serializable接口,而如果B没有实现Serializable接口,那么B一定要提供无参构造函数否则反序列的时候会报错:


6. 反序列化单例对象与readResolve

先看下面这个例子:
package aty;

import java.io.Serializable;

public class Student implements Serializable {

	private static final long serialVersionUID = 1L;

	private int id;

	private String name;

	private Sides side;

	public Student(int id, String name, Sides side) {
		this.id = id;
		this.name = name;
		this.side = side;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", side=" + side + "]";
	}

	public Sides getSide() {
		return side;
	}
}
package aty;

import java.io.Serializable;

public class Sides implements Serializable {

	private static final long serialVersionUID = 1L;

	private int value;

	private Sides(int newVal) {
		value = newVal;
	}

	private static final int LEFT_VALUE = 1;
	private static final int RIGHT_VALUE = 2;
	private static final int TOP_VALUE = 3;
	private static final int BOTTOM_VALUE = 4;

	public static final Sides LEFT = new Sides(LEFT_VALUE);
	public static final Sides RIGHT = new Sides(RIGHT_VALUE);
	public static final Sides TOP = new Sides(TOP_VALUE);
	public static final Sides BOTTOM = new Sides(BOTTOM_VALUE);

	@Override
	public String toString() {
		switch (value) {
		case 1:
			return "left";
		case 2:
			return "right";
		case 3:
			return "top";
		case 4:
			return "bottom";
		}

		return "error";
	}

}

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

import aty.Sides;
import aty.Student;

public class Utils {

	public static void main(String[] args) throws Exception {

		serialize("c:/1.obj", new Student(1, "11", Sides.LEFT));
		serialize("c:/2.obj", new Student(2, "22", Sides.LEFT));

		Student s1 = (Student) deserialize("c:/1.obj");
		Student s2 = (Student) deserialize("c:/2.obj");

		System.out.println(s1.getSide() == s2.getSide());
	}

	public static void serialize(String filePath, Object object)
			throws IOException {
		FileOutputStream fout = new FileOutputStream(filePath);
		ObjectOutputStream oos = new ObjectOutputStream(fout);
		oos.writeObject(object);
		oos.close();
	}

	public static Object deserialize(String filePath) throws Exception {
		FileInputStream fis = new FileInputStream(filePath);
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object result = ois.readObject();
		ois.close();
		return result;
	}
}

可以看到执行结果是false,也就是说反序列化单例对象的时候出现了问题。我们怎么样才能正确地反序列化单例对象呢?使用readResolve,该方法允许class在反序列化返回对象前替换、解析在流中读出来的对象。实现readResolve方法,一个class可以直接控制反序化返回的类型和对象引用。 
package aty;

import java.io.ObjectStreamException;
import java.io.Serializable;

public class Sides implements Serializable {

	private static final long serialVersionUID = 1L;

	private int value;

	private Sides(int newVal) {
		value = newVal;
	}

	private static final int LEFT_VALUE = 1;
	private static final int RIGHT_VALUE = 2;
	private static final int TOP_VALUE = 3;
	private static final int BOTTOM_VALUE = 4;

	public static final Sides LEFT = new Sides(LEFT_VALUE);
	public static final Sides RIGHT = new Sides(RIGHT_VALUE);
	public static final Sides TOP = new Sides(TOP_VALUE);
	public static final Sides BOTTOM = new Sides(BOTTOM_VALUE);

	@Override
	public String toString() {
		switch (value) {
		case 1:
			return "left";
		case 2:
			return "right";
		case 3:
			return "top";
		case 4:
			return "bottom";
		}

		return "error";
	}

	private Object readResolve() throws ObjectStreamException {
		switch (value) {
		case LEFT_VALUE:
			return LEFT;
		case RIGHT_VALUE:
			return RIGHT;
		case TOP_VALUE:
			return TOP;
		case BOTTOM_VALUE:
			return BOTTOM;
		}
		return null;
	}

}
我们添加了一个readResolve,当反序列化的时候,这个方法会自动被调用。


7. writeObject和readObject定制序列化

如果一个实现了Serializable接口的类,提供了writeObject 和 readObject方法,那么这2个方法会自动被java调用。
package aty;

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

public class Student implements Serializable {

	private static final long serialVersionUID = 1L;

	private int id;

	private transient String name;

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}

	private void writeObject(ObjectOutputStream oos) throws IOException {
		oos.defaultWriteObject();
		oos.writeUTF(name);
		System.out.println("serialized");
	}

	private void readObject(ObjectInputStream ois) throws IOException,
			ClassNotFoundException {
		ois.defaultReadObject();
		name = ois.readUTF();
		System.out.println("deserialized");
	}
}
我们将name字段标记成transient的了,但是又通过代码将name序列化到文件中了。我们举的这个例子没有什么实际用处,最典型的应用莫过于JDK中的ArrayList了,可以参考这篇“深入分析Java的序列化与反序列化”文章。

8.使用Externalizable

上面我们看到可以使用writeObject和readObject来控制序列化的过程,Externalizable功能与之类似。使用readObject和writeObject,我们必须自己写函数签名。实现Externalizable接口,可以自动生成函数签名。
package aty;

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

public class Student implements Externalizable {

	private static final long serialVersionUID = 1L;

	private int id;

	private transient String name;

	public Student() {
	}

	public Student(int id, String name) {
		this.id = id;
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}

	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(id);
		out.writeUTF(name);
	}

	@Override
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		id = in.readInt();
		name = in.readUTF();

	}
}


参考文章:

深入分析Java的序列化与反序列化 http://ms.csdn.net/geek/54761


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值