zz from http://www.javaeye.com/topic/71035 writer:jonsamwang
簡單來說序列化就是一種用來處理物件流程的機制,所謂物件流程也就是將對象的內容進行流化,流的概念這裡不用多說(就是I/O),我們可以對流化後的對象進行讀寫操作,也可將流化後的對象傳輸於網路之間(註:要想將對象傳輸於網路必須進行流化)!在對物件流程進行讀寫操作時會引發一些問題,而序列化機制正是用來解決這些問題的!
問題的引出:
如上所述,讀寫對象會有什麼問題呢?比如:我要將對象寫入一個磁碟檔案而後再將其讀出來會有什麼問題嗎?別急,其中一個最大的問題就是對象引用!舉個例子來說:假如我有兩個類,分別是A和B,B類中含有一個指向A類對象的引用,現在我們對兩個類進行執行個體化{ A a = new A(); B b = new B(); },這時在記憶體中實際上分配了兩個空間,一個儲存物件a,一個儲存物件b,接下來我們想將它們寫入到磁碟的一個檔案中去,就在寫入檔案時出現了問題!因為對象b包含對對象a的引用,所以系統會自動的將a的資料複製一份到b中,這樣的話當我們從檔案中恢複對象時(也就是重新載入到記憶體中)時,記憶體配置了三個空間,而對象a同時在記憶體中存在兩份,想一想後果吧,如果我想修改對象a的資料的話,那不是還要搜尋它的每一份拷貝來達到對象資料的一致性,這不是我們所希望的!
以下序列化機制的解決方案:
1.儲存到磁碟的所有對象都獲得一個序號(1, 2, 3等等)<注,這裡的序號應該不是serialVersionUID>
2.當要儲存一個對象時,先檢查該對象是否被儲存了。
3.如果以前儲存過,只需寫入"與已經儲存的具有序號x的對象相同"的標記,否則,儲存該對象
通過以上的步驟序列化機制解決了對象引用的問題!
序列化的實現:
將需要被序列化的類實現Serializable介面,該介面沒有需要實現的方法,implements Serializable只是為了標註該對象是可被序列化的,然後使用一個輸出資料流(如:FileOutputStream)來構造一個ObjectOutputStream(物件流程)對象,接著,使用ObjectOutputStream對象的writeObject(Object obj)方法就可以將參數為obj的對象寫出(即儲存其狀態),要恢複的話則用輸入資料流。
例子:
import java.io.*;
public class Test
{
public static void main(String[] args)
{
Employee harry = new Employee("Harry Hacker", 50000);
Manager manager1 = new Manager("Tony Tester", 80000);
manager1.setSecretary(harry);
Employee[] staff = new Employee[2];
staff[0] = harry;
staff[1] = manager1;
try
{
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("employee.dat"));
out.writeObject(staff);
out.close();
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("employee.dat"));
Employee[] newStaff = (Employee[])in.readObject();
in.close();
/**
*通過harry對象來加薪
*將在secretary上反映出來
*/
newStaff[0].raiseSalary(10);
for (int i = 0; i < newStaff.length; i++)
System.out.println(newStaff[i]);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class Employee implements Serializable
{
public Employee(String n, double s)
{
name = n;
salary = s;
}
/**
*加薪水
*/
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public String toString()
{
return getClass().getName()
+ "[name = "+ name
+ ",salary = "+ salary
+ "]";
}
private String name;
private double salary;
}
class Manager extends Employee
{
public Manager(String n, double s)
{
super(n, s);
secretary = null;
}
/**
*設定秘書
*/
public void setSecretary(Employee s)
{
secretary = s;
}
public String toString()
{
return super.toString()
+ "[secretary = "+ secretary
+ "]";
}
//secretary代表秘書
private Employee secretary;
}
修改預設的序列化機制:
在序列化的過程中,有些資料欄位我們不想將其序列化,對於此類欄位我們只需要在定義時給它加上transient關鍵字即可,對於transient欄位序列化機制會跳過不會將其寫入檔案,當然也不可被恢複。但有時我們想將某一欄位序列化,但它在SDK中的定義卻是不可序列化的類型,這樣的話我們也必須把他標註為transient,可是不能寫入又怎麼恢複呢?好在序列化機製為包含這種特殊問題的類提供了如下的方法定義:
private void readObject(ObjectInputStream in) throws
IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) throws
IOException;
(註:這些方法定義時必須是私人的,因為不需要你顯示調用,序列化機制會自動調用的)
使用以上方法我們可以手動對那些你又想序列化又不可以被序列化的資料欄位進行寫出和讀入操作。
下面是一個典型的例子,java.awt.geom包中的Point2D.Double類就是不可序列化的,因為該類沒有實現Serializable介面,在我的例子中將把它當作LabeledPoint類中的一個資料欄位,並示範如何將其序列化!
import java.io.*;
import java.awt.geom.*;
public class TransientTest
{
public static void main(String[] args)
{
LabeledPoint label = new LabeledPoint("Book", 5.00, 5.00);
try
{
System.out.println(label);//寫入前
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("Label.txt"));
out.writeObject(label);
out.close();
System.out.println(label);//寫入後
ObjectInputStream in = new ObjectInputStream(new
FileInputStream("Label.txt"));
LabeledPoint label1 = (LabeledPoint)in.readObject();
in.close();
System.out.println(label1);//讀出並加1.0後
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
class LabeledPoint implements Serializable
{
public LabeledPoint(String str, double x, double y)
{
label = str;
point = new Point2D.Double(x, y);
}
private void writeObject(ObjectOutputStream out) throws IOException
{
/**
*必須通過調用defaultWriteObject()方法來寫入
*對象的描述以及那些可以被序列化的欄位
*/
out.defaultWriteObject();
out.writeDouble(point.getX());
out.writeDouble(point.getY());
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
/**
*必須調用defaultReadObject()方法
*/
in.defaultReadObject();
double x = in.readDouble() + 1.0;
double y = in.readDouble() + 1.0;
point = new Point2D.Double(x, y);
}
public String toString()
{
return getClass().getName()
+ "[label = "+ label
+ ", point.getX() = "+ point.getX()
+ ", point.getY() = "+ point.getY()
+ "]";
}
private String label;
transient private Point2D.Double point;
}