存取程式狀態的幾種方法——Java I/O應用雜談
最後更新:2017-02-28
來源:互聯網
上載者:User
程式
jungleford如是說
已經有一個多月沒有搭理blog了,原因很多,譬如實驗室的項目正在收工,巨忙;譬如找工作及其相關的事情;而且二月份大部分時間是陪老爹老媽,家裡撥號的速度可想而知……但主要還是沒有找到一個合適的topic,或者說這段時間懶了(臨畢業前期綜合症),淨在看《漢武大帝》和曆史方面的書,還有其它亂七八糟的閑書,就是沒有認真地玩Java,哈哈!現在工作差不多落實了,好在不算太爛,小資青年jungleford的生活又開始步入正軌了!以上是新年裡的一些廢話。 今天稍微聊一點關於“程式狀態儲存”方面的問題,我們很容易就會想到“序列化”(Serialization,有的書上又翻譯為“順序化”或者“序列化”,但“串列”一詞總是讓我聯想到通訊和硬體介面,所以我更習慣於“序列化”的叫法,何況這種叫法是有來頭的,後面我會談到這個名稱的由來),當然,序列化是一種方便有效資料存取方式,但它還有更加廣泛的應用。廣義上講,就是討論一下I/O的一些應用。
檔案I/O:檔案流→序列化
★檔案流 檔案操作是最簡單最直接也是最容易想到的一種方式,我們說的檔案操作不僅僅是通過FileInputStream/FileOutputStream這麼“裸”的方式直接把資料寫入到本地檔案(像我以前寫的一個掃雷的小遊戲JavaMine就是這樣儲存一局的狀態的),這樣就比較“底層”了。
主要類與方法描述FileInputStream.read()從本地檔案讀取二進位格式的資料FileReader.read()從本地檔案讀取字元(文本)資料FileOutputStream.write()儲存位元據到本地檔案FileWriter.write()儲存字元資料到本地檔案
★XML 和上面的單純的I/O方式相比,XML就顯得“高檔”得多,以至於成為一種資料交換的標準。以DOM方式為例,它關心的是首先在記憶體中構造文檔樹,資料儲存在某個結點上(可以是葉子結點,也可以是標籤結點的屬性),構造好了以後一次性的寫入到外部檔案,但我們只需要知道檔案的位置,並不知道I/O是怎麼操作的,XML操作方式可能多數人也實踐過,所以這裡也只列出相關的方法,供初學者預先瞭解一下。主要的包是javax.xml.parsers,org.w3c.dom,javax.xml.transform。
主要類與方法描述DocumentBuilderFactory.newDocumentBuilder().parse()解析一個外部的XML檔案,得到一個Document對象的DOM樹DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()初始化一棵DOM樹Document.getDocumentElement(). appendChild()為一個標籤結點添加一個子結點Document.createTextNode()產生一個字串結點Node.getChildNodes()取得某個結點的所有下一層子結點Node.removeChild() 刪除某個結點的子結點Document. getElementsByTagName()尋找所有指定名稱的標籤結點Document.getElementById()尋找指定名稱的一個標籤結點,如果有多個符合,則返回某一個,通常是第一個Element.getAttribute()取得一個標籤的某個屬性的的值Element.setAttribute()設定一個標籤的某個屬性的的值Element.removeAttribute()刪除一個標籤的某個屬性TransformerFactory.newInstance().newTransformer().transform()將一棵DOM樹寫入到外部XML檔案
★序列化 使用基本的檔案讀寫方式存取資料,如果我們僅僅儲存相同類型的資料,則可以用同一種格式儲存,譬如在我的JavaMine中儲存一個盤局時,需要儲存每一個方格的座標、是否有地雷,是否被翻開等,這些資訊組合成一個“複合類型”;相反,如果有多種不同類型的資料,那我們要麼把它分解成若干部分,以相同類型(譬如String)儲存,要麼我們需要在程式中添加解析不同類型資料格式的邏輯,這就很不方便。於是我們期望用一種比較“高”的層次上處理資料,程式員應該花儘可能少的時間和代碼對資料進行解析,事實上,序列化操作為我們提供了這樣一條途徑。 序列化(Serialization)大家可能都有所接觸,它可以把對象以某種特定的編碼格式寫入或從外部位元組流(即ObjectInputStream/ObjectOutputStream)中讀取。序列化一個對象非常之簡單,僅僅實現一下Serializable介面即可,甚至都不用為它專門添加任何方法:
public class MySerial implements java.io.Serializable{ ...}
但有一個條件:即你要序列化的類當中,它的每個屬性都必須是是“可序列化”的。這句話說起來有點拗口,其實所有基本類型(就是int,char,boolean之類的)都是“可序列化”的,而你可以看看JDK文檔,會發現很多類其實已經實現了Serializable(即已經是“可序列化”的了),於是這些類的對象以及基礎資料型別 (Elementary Data Type)都可以直接作為你需要序列化的那個類的內部屬性。如果碰到了不是“可序列化”的屬性怎麼辦?對不起,那這個屬性的類還需要事先實現Serializable介面,如此遞迴,直到所有屬性都是“可序列化”的。
主要類與方法描述ObjectOutputStream.writeObject()將一個對象序列化到外部位元組流ObjectInputStream.readObject()從外部位元組流讀取並重新構造對象
從實際應用上看來,“Serializable”這個介面並沒有定義任何方法,彷彿它只是一個標記(或者說像是Java的關鍵字)而已,一旦虛擬機器看到這個“標記”,就會嘗試調用自身預定義的序列化機制,除非你在實現Serializable介面的同時還定義了私人的readObject()或writeObject()方法。這一點很奇怪。不過你要是不願意讓系統使用預設的方式進行序列化,那就必須定義上面提到的兩個方法:
public class MySerial implements java.io.Serializable{ private void writeObject(java.io.ObjectOutputStream out) throws IOException { ... } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { ... } ...}
譬如你可以在上面的writeObject()裡調用預設的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不願意將某些敏感的屬性和資訊序列化,你也可以調用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性。關於使用者可定製的序列化方法,我們將在後面提到。
★Bean 上面的序列化只是一種基本應用,你把一個對象序列化到外部檔案以後,用notepad開啟那個檔案,只能從為數不多的一些可讀字元中猜到這是有關這個類的資訊檔,這需要你熟悉序列化檔案的位元組編碼方式,那將是比較痛苦的(在《Core Java 2》第一卷裡提到了相關編碼方式,有興趣的話可以查看參考資料),某些情況下我們可能需要被序列化的檔案具有更好的可讀性。另一方面,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開始,其規範裡也要求支援文本方式的“長期的持久化”(long-term persistence)。 開啟JDK文檔,java.beans包裡的有一個名為“Encoder”的類,這就是一個可以序列化bean的實用類。和它相關的兩個主要類有XMLEcoder和XMLDecoder,顯然,這是以XML檔案的格式儲存和讀取bean的工具。他們的用法也很簡單,和上面ObjectOutputStream/ObjectInputStream比較類似。
主要類與方法描述XMLEncoder.writeObject()將一個對象序列化到外部位元組流XMLDecoder.readObject()從外部位元組流讀取並重新構造對象
如果一個bean是如下格式:
public class MyBean{ int i; char[] c; String s; ...(get和set操作省略)...}
那麼通過XMLEcoder序列化出來的XML檔案具有這樣的形式:
<?xml version="1.0" encoding="UTF-8"?><java version="1.4.0" class="java.beans.XMLDecoder"> <object class="MyBean"> <void property="i"> <int>1</int> </void> <void property="c"> <array class="char" length="3"> <void index="0"> <int>a</int> </void> <void index="1"> <int>b</int> </void> <void index="2"> <int>c</int> </void> </array> </void> <void property="s"> <string>fox jump!</string> </void> </object></java>
像AWT和Swing中很多可視化組件都是bean,當然也是可以用這種方式序列化的,下面就是從JDK文檔中摘錄的一個JFrame序列化以後的XML檔案:
<?xml version="1.0" encoding="UTF-8"?><java version="1.0" class="java.beans.XMLDecoder"> <object class="javax.swing.JFrame"> <void property="name"> <string>frame1</string> </void> <void property="bounds"> <object class="java.awt.Rectangle"> <int>0</int> <int>0</int> <int>200</int> <int>200</int> </object> </void> <void property="contentPane"> <void method="add"> <object class="javax.swing.JButton"> <void property="label"> <string>Hello</string> </void> </object> </void> </void> <void property="visible"> <boolean>true</boolean> </void> </object></java>
因此但你想要儲存的資料是一些不是太複雜的類型的話,把它做成bean再序列化也不失為一種方便的選擇。
★Properties 在以前我總結的一篇關於集合架構的小文章裡提到過,Properties是曆史集合類的一個典型的例子,這裡主要不是介紹它的集合特性。大家可能都經常接觸一些設定檔,如Windows的ini檔案,Apache的conf檔案,還有Java裡的properties檔案等,這些檔案當中的資料以“關鍵字-值”對的方式儲存。“環境變數”這個概念都知道吧,它也是一種“key-value”對,以前也常常看到版上問“如何取得系統某某資訊”之類的問題,其實很多都儲存在環境變數裡,只要用一條
System.getProperties().list(System.out);
就能獲得全部環境變數的列表:
-- listing properties --java.runtime.name=Java(TM) 2 Runtime Environment, Stand...sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\binjava.vm.version=1.4.2_05-b04java.vm.vendor=Sun Microsystems Inc.java.vendor.url=http://java.sun.com/path.separator=;java.vm.name=Java HotSpot(TM) Client VMfile.encoding.pkg=sun.iouser.country=CNsun.os.patch.level=Service Pack 1java.vm.specification.name=Java Virtual Machine Specificationuser.dir=d:\my documents\項目\eclipse WTDemojava.runtime.version=1.4.2_05-b04java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironmentjava.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li...os.arch=x86java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.user.variant=os.name=Windows XPsun.java2d.fontpath=java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi...java.specification.name=Java Platform API Specificationjava.class.version=48.0java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac...os.version=5.1user.home=D:\Users\cn2lx0q0user.timezone=java.awt.printerjob=sun.awt.windows.WPrinterJobfile.encoding=GBKjava.specification.version=1.4user.name=cn2lx0q0java.class.path=d:\my documents\項目\eclipse WTDemo\bi...java.vm.specification.version=1.0sun.arch.data.model=32java.home=C:\Program Files\Java\j2re1.4.2_05java.specification.vendor=Sun Microsystems Inc.user.language=zhawt.toolkit=sun.awt.windows.WToolkitjava.vm.info=mixed modejava.version=1.4.2_05java.ext.dirs=C:\Program Files\Java\j2re1.4.2_05\li...sun.boot.class.path=C:\Program Files\Java\j2re1.4.2_05\li...java.vendor=Sun Microsystems Inc.file.separator=\java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...sun.cpu.endian=littlesun.io.unicode.encoding=UnicodeLittlesun.cpu.isalist=pentium i486 i386
主要類與方法描述load()從一個外部流讀取屬性store()將屬性儲存到外部流(特別是檔案)getProperty()取得一個指定的屬性setProperty()設定一個指定的屬性list()列出這個Properties對象包含的全部“key-value”對System.getProperties()取得系統當前的環境變數
你可以這樣儲存一個properties檔案:
Properties prop = new Properties();prop.setProperty("key1", "value1");...FileOutputStream out = new FileOutputStream("config.properties");prop.store(out, "--這裡是檔案頭,可以加入注釋--");
★Preferences 如果我說Java裡面可以不使用JNI的手段操作Windows的註冊表你信不信?很多軟體的菜單裡都有“Setting”或“Preferences”這樣的選項用來設定或修改軟體的配置,這些配置資訊可以儲存到一個像上面所述的設定檔當中,如果是Windows平台下,也可能會儲存到系統註冊表中。從JDK 1.4開始,Java在java.util下加入了一個專門處理使用者和系統配置資訊的java.util.prefs包,其中一個類Preferences是一種比較“進階”的玩意。從本質上講,Preferences本身是一個與平台無關的東西,但不同的OS對它的SPI(Service Provider Interface)的實現卻是與平台相關的,因此,在不同的系統中你可能看到喜好設定儲存為本地檔案、LDAP目錄項、資料庫條目等,像在Windows平台下,它就儲存到了系統註冊表中。不僅如此,你還可以把喜好設定匯出為XML檔案或從XML檔案匯入。
主要類與方法描述systemNodeForPackage()根據指定的Class對象得到一個Preferences對象,這個對象的註冊表路徑是從“HKEY_LOCAL_MACHINE\”開始的systemRoot()得到以註冊表路徑HKEY_LOCAL_MACHINE OFTWARE\Javasoft\Prefs 為根結點的Preferences對象userNodeForPackage()根據指定的Class對象得到一個Preferences對象,這個對象的註冊表路徑是從“HKEY_CURRENT_USER\”開始的userRoot()得到以註冊表路徑HKEY_CURRENT_USER OFTWARE\Javasoft\Prefs 為根結點的Preferences對象putXXX()設定一個屬性的值,這裡XXX可以為基本數值型類型,如int、long等,但首字母大寫,表示參數為相應的類型,也可以不寫而直接用put,參數則為字串getXXX()得到一個屬性的值exportNode()將全部喜好設定匯出為一個XML檔案exportSubtree()將部分喜好設定匯出為一個XML檔案importPreferences()從XML檔案匯入喜好設定
你可以按如下步驟儲存資料:
Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 這種方法是在“HKEY_CURRENT_USER\”下按當前類的路徑建立一個登錄機碼Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 這種方法是在“HKEY_LOCAL_MACHINE\”下按當前類的路徑建立一個登錄機碼Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_CURRENT_USER OFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個登錄機碼Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE OFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個登錄機碼myPrefs1.putInt("key1", 10);myPrefs1.putDouble("key2", -7.15);myPrefs1.put("key3", "value3");FileOutputStream out = new FileOutputStream("prefs.xml");myPrefs1.exportNode(out);
網路I/O:Socket→RMI
★Socket Socket編程可能大家都很熟,所以就不多討論了,只是說通過socket把資料儲存到遠端伺服器或從網路socket讀取資料也不失為一種值得考慮的方式。
★RMI RMI機制其實就是RPC(遠端程序呼叫)的Java版本,它使用socket作為基本傳輸手段,同時也是序列化最重要的一個應用。現在網路傳輸從編程的角度來看基本上都是以流的方式操作,socket就是一個例子,將對象轉換成位元組流的一個重要目標就是為了方便網路傳輸。 想象一下傳統的單機環境下的程式設計,對於Java語言的函數(方法)調用(注意與C語言函數調用的區別)的參數傳遞,會有兩種情況:如果是基礎資料型別 (Elementary Data Type),這種情況下和C語言是一樣的,採用值傳遞方式;如果是對象,則傳遞的是對象的引用,包括傳回值也是引用,而不是一個完整的對象拷貝!試想一下在不同的虛擬機器之間進行方法調用,即使是兩個完全同名同類型的對象他們也很可能是不同的引用!此外對於方法調用過程,由於被調用過程的壓棧,記憶體“現場”完全被被調用者佔有,當被呼叫者法返回時,才將調用者的地址寫回到程式計數器(PC),恢複調用者的狀態,如果是兩個虛擬機器,根本不可能用簡單壓棧的方式來儲存調用者的狀態。因為種種原因,我們才需要建立RMI通訊實體之間的“代理”對象,譬如“存根”就相當於遠程伺服器對象在客戶機上的代理,stub就是這麼來的,當然這是後話了。 本機物件與遠程對象(未必是物理位置上的不同機器,只要不是在同一個虛擬機器內皆為“遠程”)之間傳遞參數和傳回值,可能有這麼幾種情形:
值傳遞:這又包括兩種子情形:如果是基礎資料型別 (Elementary Data Type),那麼都是“可序列化”的,統統序列化成可傳輸的位元組流;如果是對象,而且不是“遠程對象”(所謂“遠程對象”是實現了java.rmi.Remote介面的對象),本來對象傳遞的應該是引用,但由於上述原因,引用是不足以證明對象身份的,所以傳遞的仍然是一個序列化的拷貝(當然這個對象也必須滿足上述“可序列化”的條件)。 引用傳遞:可以引用傳遞的只能是“遠程對象”。這裡所謂的“引用”不要理解成了真的只是一個符號,它其實是一個留在(客戶機)本地stub中的,和遠端伺服器上那個真實的對象張得一模一樣的鏡像而已!只是因為它有點“特權”(不需要經過序列化),在本地記憶體裡已經有了一個執行個體,真正引用的其實是這個“孿生子”。 由此可見,序列化在RMI當中佔有多麼重要的地位。
資料庫I/O:CMP、Hibernate
★什麼是“Persistence” 用過VMWare的朋友大概都知道當一個guest OS正在啟動並執行時候點擊“Suspend”將虛擬OS掛起,它會把整個虛擬記憶體的內容儲存到磁碟上,譬如你為虛擬OS分配了128M的運行記憶體,那掛起以後你會在虛擬OS所在的目錄下找到一個同樣是128M的檔案,這就是虛擬OS記憶體的完整鏡像!這種記憶體的鏡像手段其實就是“Persistence”(持久化)概念的由來。
★CMP和Hibernate 因為我對J2EE的東西不是太熟悉,隨便找了點材料看看,所以擔心說的不到位,這次就不作具體總結了,人要學習……真是一件痛苦的事情 ~~~>_<~~~
序列化再探討
從以上技術的討論中我們不難體會到,序列化是Java之所以能夠出色地實現其鼓吹的兩大賣點——分布式(distributed)和跨平台(OS independent)的一個重要基礎。TIJ(即“Thinking in Java”)談到I/O系統時,把序列化稱為“lightweight persistence”——“輕量級的持久化”,這確實很有意思。
★為什麼叫做“序列”化? 開場白裡我說更習慣於把“Serialization”稱為“序列化”而不是“序列化”,這是有原因的。介紹這個原因之前先回顧一些電腦基本的知識,我們知道現代電腦的記憶體空間都是線性編址的(什麼是“線性”知道吧,就是一個元素只有一個唯一的“前驅”和唯一的“後繼”,當然頭尾元素是個例外;對於地址來說,它的下一個地址當然不可能有兩個,否則就亂套了),“地址”這個概念推廣到資料結構,就相當於“指標”,這個在本科低年級大概就知道了。注意了,既然是線性,那“地址”就可以看作是記憶體空間的“序號”,說明它的組織是有順序的,“序號”或者說“序號”正是“Serialization”機制的一種體現。為什麼這麼說呢?譬如我們有兩個對象a和b,分別是類A和B的執行個體,它們都是可序列化的,而A和B都有一個類型為C的屬性,根據前面我們說過的原則,C當然也必須是可序列化的。
import java.io.*;...class A implements Serializable{ C c; ...}
class B implements Serializable{ C c; ...}
class C implements Serializable{ ...}
A a;B b;C c1;...
注意,這裡我們在執行個體化a和b的時候,有意讓他們的c屬性使用同一個C類型對象的引用,譬如c1,那麼請試想一下,但我們序列化a和b的時候,它們的c屬性在外部位元組流(當然可以不僅僅是檔案)裡儲存的是一份拷貝還是兩份拷貝呢?序列化在這裡使用的是一種類似於“指標”的方案:它為每個被序列化的對象標上一個“序號”(serial number),但序列化一個對象的時候,如果其某個屬性對象是已經被序列化的,那麼這裡只向輸出資料流寫入該屬性的序號;從位元組流恢複被序列化的對象時,也根據序號找到對應的流來恢複。這就是“序列化”名稱的由來!這裡我們看到“序列化”和“指標”是極相似的,只不過“指標”是記憶體空間的地址鏈,而序列化用的是外部流中的“序號鏈”。 使用“序號”而不是記憶體位址來標識一個被序列化的對象,是因為從流中恢複對象到記憶體,其地址可能就未必是原來的地址了——我們需要的只是這些對象之間的參考關聯性,而不是死板的原始位置,這在RMI中就更是必要,在兩台不同的機器之間傳遞對象(流),根本就不可能指望它們在兩台機器上都具有相同的記憶體位址。
★更靈活的“序列化”:transient屬性和Externalizable Serializable確實很方便,方便到你幾乎不需要做任何額外的工作就可以輕鬆將記憶體中的對象儲存到外部。但有兩個問題使得Serializable的威力收到束縛: 一個是效率問題,《Core Java 2》中指出,Serializable使用系統預設的序列化機制會影響軟體的運行速度,因為需要為每個屬性的引用編號和查號,再加上I/O操作的時間(I/O和記憶體讀寫差的可是一個數量級的大小),其代價當然是可觀的。 另一個困擾是“裸”的Serializable不可定製,傻乎乎地什麼都給你序列化了,不管你是不是想這麼做。其實你可以有至少三種定製序列化的選擇。其中一種前面已經提到了,就是在implements Serializable的類裡面添加私人的writeObject()和readObject()方法(這種Serializable就不裸了,^_^),在這兩個方法裡,該序列化什麼,不該序列化什麼,那就由你說了算了,你當然可以在這兩個方法體裡面分別調用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執行預設的序列化動作(那你在代碼上不就做無用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法對你中意的屬性進行序列化。但虛擬機器一看到你定義了這兩個方法,它就不再用預設的機制了。 如果僅僅為了跳過某些屬性不讓它序列化,上面的動作似乎顯得麻煩,更簡單的方法是對不想序列化的屬性加上transient關鍵字,說明它是個“暫態變數”,預設序列化的時候就不會把這些屬性也塞到外部流裡了。當然,你如果定義writeObject()和readObject()方法的化,仍然可以把暫態變數進行序列化。題外話,像transient、violate、finally這樣的關鍵字初學者可能會不太重視,而現在有的公司招聘就偏偏喜歡問這樣的問題 :( 再一個方案就是不實現Serializable而改成實現Externalizable介面。我們研究一下這兩個介面的原始碼,發現它們很類似,甚至容易混淆。我們要記住的是:Externalizable預設並不儲存任何對象相關資訊!任何儲存和恢複對象的動作都是你自己定義的。Externalizable包含兩個public的方法:
public void writeExternal(ObjectOutput out) throws IOException;public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
乍一看這和上面的writeObject()和readObject()幾乎差不多,但Serializable和Externalizable走的是兩個不同的流程:Serializable在對象不存在的情況下,就可以僅憑外部的位元組序列把整個對象重建出來;但Externalizable在重建對象時,先是調用該類的預設建構函式(即不含參數的那個建構函式)使得記憶體中先有這麼一個執行個體,然後再調用readExternal方法對執行個體中的屬性進行恢複,因此,如果預設建構函式中和readExternal方法中都沒有賦值的那些屬性,特別他們是非基本類型的話,將會是空(null)。在這裡需要注意的是,transient只能用在對Serializable而不是Externalizable的實現裡面。
★序列化與複製 從“可序列化”的遞迴定義來看,一個序列化的對象貌似對象記憶體映象的外部複製,如果沒有共用引用的屬性的化,那麼應該是一個深度複製。關於複製的話題有可以談很多,這裡就不細說了,有興趣的話可以參考IBM developerWorks上的一篇文章:JAVA中的指標,引用及對象的clone
一點啟示
作為一個實際的應用,我在寫那個簡易的郵件用戶端JExp的時候曾經對比過好幾種儲存Message對象(主要是幾個關鍵屬性和郵件的內容)到本地的方法,譬如XML、Properties等,最後還是選擇了用序列化的方式,因為這種方法最簡單, 大約可算是“學以致用”罷。這裡“存取程式狀態”其實只是一個引子話題罷了,我想說的是——就如同前面我們討論的關於logging的話題一樣——在Java面前對同一個問題你可以有很多種solution:熟悉檔案操作的,你可能會覺得Properties、XML或Bean比較方便,然後又發現了還有Preferences這麼一個東東,大概又會感慨“天外有天”了,等到你接觸了很多種新方法以後,結果又會“殊途同歸”,重新反省Serialization機制本身。這不僅是Java,科學也是同樣的道理。
參考資料
Core Java 2. by Cay S. Horstmann, Gary Cornell J2SE進階. by JavaResearch.org Thinking in Java. by Bruce Eckel J2SE 1.4.2 Documentation. by java.sun.com Java Network Programming. by Elliotte R. Harold Java分布式對象:RMI和CORBA. by IBM developerWorks