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