天天看點

了解java的序列化與反序列化1. 如何序列化和反序列化?2. serialVersionUID作用是什麼?3. transient關鍵字的作用是什麼?4. 反序列化與構造函數5. 序列化與繼承6. 反序列化單例對象與readResolve7. writeObject和readObject定制序列化8.使用Externalizable

序列化是一種對象持久化的手段,普遍應用在網絡傳輸、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。

了解java的序列化與反序列化1. 如何序列化和反序列化?2. serialVersionUID作用是什麼?3. transient關鍵字的作用是什麼?4. 反序列化與構造函數5. 序列化與繼承6. 反序列化單例對象與readResolve7. writeObject和readObject定制序列化8.使用Externalizable
// 預設的serialVersionUID
private static final long serialVersionUID = 1L;
           
// eclipse按照一定算法生産的serialVersionUID
private static final long serialVersionUID = 6990027678698020472L;
           

我們平常一般都是用第一種方式來解決這個警告的。那serialVersionUID作用是什麼呢?我們知道:如果我們将aty.Student這個類的對象序列号到c:/stu.obj檔案中,那麼當我們反序列化的時候,如果classpath沒有這個類會報錯:

了解java的序列化與反序列化1. 如何序列化和反序列化?2. serialVersionUID作用是什麼?3. transient關鍵字的作用是什麼?4. 反序列化與構造函數5. 序列化與繼承6. 反序列化單例對象與readResolve7. writeObject和readObject定制序列化8.使用Externalizable

可以看到:在序列化和反序列化的時候, 類的全路徑必須完全一緻。現在我們做一個這樣的實驗: 1.設定serialVersionUID=1後,将student對象序列化到檔案中 2.将serialVersionUID修改成2後,從檔案中反序列化student對象。

那麼我們會遇到下面的異常:

了解java的序列化與反序列化1. 如何序列化和反序列化?2. serialVersionUID作用是什麼?3. transient關鍵字的作用是什麼?4. 反序列化與構造函數5. 序列化與繼承6. 反序列化單例對象與readResolve7. writeObject和readObject定制序列化8.使用Externalizable

也就是說:在序列化和反序列化的過程中,類不僅路徑要完全一緻,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一定要提供無參構造函數否則反序列的時候會報錯:

了解java的序列化與反序列化1. 如何序列化和反序列化?2. serialVersionUID作用是什麼?3. transient關鍵字的作用是什麼?4. 反序列化與構造函數5. 序列化與繼承6. 反序列化單例對象與readResolve7. writeObject和readObject定制序列化8.使用Externalizable

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

序列化-了解readResolve() http://blog.csdn.net/haydenwang8287/article/details/5964130