在網(wǎng)上看了很多有關(guān)序列化的文章,我自己也寫(xiě)了兩篇,現在感覺(jué)這些文章都沒(méi)有很好的把序列化說(shuō)清楚(包括我自己在內),所以在此我將總結前人以及自己的經(jīng)驗,用更淺顯易懂的語(yǔ)言來(lái)描述該機制,當然,仍然會(huì )有不好的地方,希望你看后可以指出,作為一名程序員應該具有不斷探索的精神和強烈的求知欲望!
序列化概述:
簡(jiǎn)單來(lái)說(shuō)序列化就是一種用來(lái)處理對象流的機制,所謂對象流也就是將對象的內容進(jìn)行流化,流的概念這里不用多說(shuō)(就是I/O),我們可以對流化后的對象進(jìn)行讀寫(xiě)操作,也可將流化后的對象傳輸于網(wǎng)絡(luò )之間(注:要想將對象傳輸于網(wǎng)絡(luò )必須進(jìn)行流化)!在對對象流進(jìn)行讀寫(xiě)操作時(shí)會(huì )引發(fā)一些問(wèn)題,而序列化機制正是用來(lái)解決這些問(wèn)題的!
問(wèn)題的引出:
如上所述,讀寫(xiě)對象會(huì )有什么問(wèn)題呢?比如:我要將對象寫(xiě)入一個(gè)磁盤(pán)文件而后再將其讀出來(lái)會(huì )有什么問(wèn)題嗎?別急,其中一個(gè)最大的問(wèn)題就是對象引用!舉個(gè)例子來(lái)說(shuō):假如我有兩個(gè)類(lèi),分別是A和B,B類(lèi)中含有一個(gè)指向A類(lèi)對象的引用,現在我們對兩個(gè)類(lèi)進(jìn)行實(shí)例化{ A a = new A(); B b =new B();},這時(shí)在內存中實(shí)際上分配了兩個(gè)空間,一個(gè)存儲對象a,一個(gè)存儲對象b,接下來(lái)我們想將它們寫(xiě)入到磁盤(pán)的一個(gè)文件中去,就在寫(xiě)入文件時(shí)出現了問(wèn)題!因為對象b包含對對象a的引用,所以系統會(huì )自動(dòng)的將a的數據復制一份到b中,這樣的話(huà)當我們從文件中恢復對象時(shí)(也就是重新加載到內存中)時(shí),內存分配了三個(gè)空間,而對象a同時(shí)在內存中存在兩份,想一想后果吧,如果我想修改對象a的數據的話(huà),那不是還要搜索它的每一份拷貝來(lái)達到對象數據的一致性,這不是我們所希望的!
以下序列化機制的解決方案:
1.保存到磁盤(pán)的所有對象都獲得一個(gè)序列號(1, 2, 3等等)
2.當要保存一個(gè)對象時(shí),先檢查該對象是否被保存了。
3.如果以前保存過(guò),只需寫(xiě)入"與已經(jīng)保存的具有序列號x的對象相同"的標記,否則,保存該對象
通過(guò)以上的步驟序列化機制解決了對象引用的問(wèn)題!
序列化的實(shí)現:
將需要被序列化的類(lèi)實(shí)現Serializable接口,該接口沒(méi)有需要實(shí)現的方法,implementsSerializable只是為了標注該對象是可被序列化的,然后使用一個(gè)輸出流(如:FileOutputStream)來(lái)構造一個(gè)ObjectOutputStream(對象流)對象,接著(zhù),使用ObjectOutputStream對象的writeObject(Objectobj)方法就可以將參數為obj的對象寫(xiě)出(即保存其狀態(tài)),要恢復的話(huà)則用輸入流。
例子:
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();
/**
*通過(guò)harry對象來(lái)加薪
*將在secretary上反映出來(lái)
*/
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;
}
/**
*設置秘書(shū)
*/
public void setSecretary(Employee s)
{
secretary = s;
}
public String toString()
{
return super.toString()
+ "[secretary = "+ secretary
+ "]";
}
//secretary代表秘書(shū)
private Employee secretary;
}
修改默認的序列化機制:
在序列化的過(guò)程中,有些數據字段我們不想將其序列化,對于此類(lèi)字段我們只需要在定義時(shí)給它加上transient關(guān)鍵字即可,對于transient字段序, 列化機制會(huì )跳過(guò)不會(huì )將其寫(xiě)入文件,當然也不可被恢復。但有時(shí)我們想將某一字段序列化,但它在SDK中的定義卻是不可序列化的類(lèi)型,這樣的話(huà)我們也必須把他標注為transient,可是不能寫(xiě)入又怎么恢復呢?好在序列化機制為包含這種特殊問(wèn)題的類(lèi)提供了如下的方法定義:
private void readObject(ObjectInputStream in) IOException, ClassNotFoundException;
private void writeObject(ObjectOutputStream out) IOException;
(注:這些方法定義時(shí)必須是私有的,因為不需要你顯示調用,序列化機制會(huì )自動(dòng)調用的)
使用以上方法我們可以手動(dòng)對那些你又想序列化又不可以被序列化的數據字段進(jìn)行寫(xiě)出和讀入操作。
下面是一個(gè)典型的例子,java.awt.geom包中的Point2D.Double類(lèi)就是不可序列化的,因為該類(lèi)沒(méi)有實(shí)現Serializable接口,在我的例子中將把它當作LabeledPoint類(lèi)中的一個(gè)數據字段,并演示如何將其序列化!
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);//寫(xiě)入前
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("Label.txt"));
out.writeObject(label);
out.close();
System.out.println(label);//寫(xiě)入后
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
{
/**
*必須通過(guò)調用defaultWriteObject()方法來(lái)寫(xiě)入
*對象的描述以及那些可以被序列化的字段
*/
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;
}
補充:
1. java.beans.XMLEncoder, 可簡(jiǎn)單地將bean對象直接轉換為XML串/流;
java.beans.XMLDecoder, 實(shí)現上述的逆操作;
優(yōu)點(diǎn):內置于JDK中,無(wú)需額外的包;
支持通用集合類(lèi)(List,Set,Map...);
使用簡(jiǎn)便;
缺點(diǎn):若要轉換的bean中有String屬性,且字符串為漢字時(shí), 轉換后可能出現格式錯誤, 此時(shí)無(wú)法恢復成bean對象;
(查看了源碼,發(fā)現轉換串時(shí)默認編碼格式為UTF-8,且此默認值為private static,無(wú)法由外部調用來(lái)修改)
2. XStream, 開(kāi)源庫,實(shí)現類(lèi)似上述的操作;(http://xstream.codehaus.org/index.html)
聯(lián)系客服