今天稍微聊一點關(guān)于“程序狀態(tài)保存”方面的問題,我們很容易就會想到“序列化”(Serialization,,有的書上又翻譯為“順序化”或者“串行化”,,但“串行”一詞總是讓我聯(lián)想到通信和硬件接口,所以我更習慣于“序列化”的叫法,何況這種叫法是有來頭的,,后面我會談到這個名稱的由來),,當然,序列化是一種方便有效的數(shù)據(jù)存取方式,,但它還有更加廣泛的應用,。廣義上講,就是討論一下I/O的一些應用,。
文件I/O:文件流→序列化
★文件流 文件操作是最簡單最直接也是最容易想到的一種方式,,我們說的文件操作不僅僅是通過FileInputStream/FileOutputStream這么“裸”的方式直接把數(shù)據(jù)寫入到本地文件(像我以前寫的一個掃雷的小游戲JavaMine就是這樣保存一局的狀態(tài)的),這樣就比較“底層”了,。 |
主要類與方法和描述
- FileInputStream.read() //從本地文件讀取二進制格式的數(shù)據(jù)
- FileReader.read() //從本地文件讀取字符(文本)數(shù)據(jù)
- FileOutputStream.write() //保存二進制數(shù)據(jù)到本地文件
- FileWriter.write() //保存字符數(shù)據(jù)到本地文件
★XML 和上面的單純的I/O方式相比,,XML就顯得“高檔”得多,,以至于成為一種數(shù)據(jù)交換的標準,。以DOM方式為例,它關(guān)心的是首先在內(nèi)存中構(gòu)造文檔樹,,數(shù)據(jù)保存在某個結(jié)點上(可以是葉子結(jié)點,,也可以是標簽結(jié)點的屬性),構(gòu)造好了以后一次性的寫入到外部文件,,但我們只需要知道文件的位置,,并不知道I/O是怎么操作的,XML操作方式可能多數(shù)人也實踐過,,所以這里也只列出相關(guān)的方法,,供初學者預先了解一下。主要的包是javax.xml.parsers,,org.w3c.dom,,javax.xml.transform。
主要類與方法和描述
- DocumentBuilderFactory.newDocumentBuilder().parse() //解析一個外部的XML文件,,得到一個Document對象的DOM樹
- DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() //初始化一棵DOM樹
- Document.getDocumentElement().appendChild() //為一個標簽結(jié)點添加一個子結(jié)點
- Document.createTextNode() //生成一個字符串結(jié)點
- Node.getChildNodes() //取得某個結(jié)點的所有下一層子結(jié)點
- Node.removeChild() //刪除某個結(jié)點的子結(jié)點
- Document.getElementsByTagName() 查找所有指定名稱的標簽結(jié)點
- Document.getElementById() //查找指定名稱的一個標簽結(jié)點,,如果有多個符合,則返回某一個,,通常是第一個
- Element.getAttribute() //取得一個標簽的某個屬性的的值
- Element.setAttribute() //設(shè)置一個標簽的某個屬性的的值
- Element.removeAttribute() //刪除一個標簽的某個屬性
- TransformerFactory.newInstance().newTransformer().transform() //將一棵DOM樹寫入到外部XML文件
★序列化 使用基本的文件讀寫方式存取數(shù)據(jù),,如果我們僅僅保存相同類型的數(shù)據(jù),則可以用同一種格式保存,,譬如在我的JavaMine中保存一個盤局時,,需要保存每一個方格的坐標、是否有地雷,,是否被翻開等,,這些信息組合成一個“復合類型”;相反,如果有多種不同類型的數(shù)據(jù),,那我們要么把它分解成若干部分,,以相同類型(譬如String)保存,要么我們需要在程序中添加解析不同類型數(shù)據(jù)格式的邏輯,,這就很不方便,。于是我們期望用一種比較“高”的層次上處理數(shù)據(jù),程序員應該花盡可能少的時間和代碼對數(shù)據(jù)進行解析,,事實上,,序列化操作為我們提供了這樣一條途徑。 序列化(Serialization)大家可能都有所接觸,,它可以把對象以某種特定的編碼格式寫入或從外部字節(jié)流(即ObjectInputStream/ObjectOutputStream)中讀取,。序列化一個對象非常之簡單,僅僅實現(xiàn)一下Serializable接口即可,,甚至都不用為它專門添加任何方法:
- public class MySerial implements java.io.Serializable
- {
- //...
- }
但有一個條件:即你要序列化的類當中,,它的每個屬性都必須是是“可序列化”的。這句話說起來有點拗口,,其實所有基本類型(就是int,,char,boolean之類的)都是“可序列化”的,,而你可以看看JDK文檔,,會發(fā)現(xiàn)很多類其實已經(jīng)實現(xiàn)了Serializable(即已經(jīng)是“可序列化”的了),于是這些類的對象以及基本數(shù)據(jù)類型都可以直接作為你需要序列化的那個類的內(nèi)部屬性,。如果碰到了不是“可序列化”的屬性怎么辦,?對不起,那這個屬性的類還需要事先實現(xiàn)Serializable接口,,如此遞歸,,直到所有屬性都是“可序列化”的。
主要類與方法和描述
- ObjectOutputStream.writeObject() //將一個對象序列化到外部字節(jié)流
- ObjectInputStream.readObject() //從外部字節(jié)流讀取并重新構(gòu)造對象
從實際應用上看來,,“Serializable”這個接口并沒有定義任何方法,,仿佛它只是一個標記(或者說像是Java的關(guān)鍵字)而已,一旦虛擬機看到這個“標記”,,就會嘗試調(diào)用自身預定義的序列化機制,,除非你在實現(xiàn)Serializable接口的同時還定義了私有的readObject()或writeObject()方法。這一點很奇怪,。不過你要是不愿意讓系統(tǒng)使用缺省的方式進行序列化,,那就必須定義上面提到的兩個方法:
譬如你可以在上面的writeObject()里調(diào)用默認的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意將某些敏感的屬性和信息序列化,你也可以調(diào)用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性,。關(guān)于用戶可定制的序列化方法,,我們將在后面提到,。
★Bean 上面的序列化只是一種基本應用,你把一個對象序列化到外部文件以后,,用notepad打開那個文件,,只能從為數(shù)不多的一些可讀字符中猜到這是有關(guān)這個類的信息文件,這需要你熟悉序列化文件的字節(jié)編碼方式,,那將是比較痛苦的(在《Core Java 2》第一卷里提到了相關(guān)編碼方式,,有興趣的話可以查看參考資料),某些情況下我們可能需要被序列化的文件具有更好的可讀性,。另一方面,,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開始,,其規(guī)范里也要求支持文本方式的“長期的持久化”(long-term persistence),。
打開JDK文檔,java.beans包里的有一個名為“Encoder”的類,,這就是一個可以序列化bean的實用類,。和它相關(guān)的兩個主要類有XMLEcoder和XMLDecoder,顯然,,這是以XML文件的格式保存和讀取bean的工具,。他們的用法也很簡單,,和上面ObjectOutputStream/ObjectInputStream比較類似,。
主要類與方法和描述
- XMLEncoder.writeObject() //將一個對象序列化到外部字節(jié)流
- XMLDecoder.readObject() //從外部字節(jié)流讀取并重新構(gòu)造對象
如果一個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>
因此但你想要保存的數(shù)據(jù)是一些不是太復雜的類型的話,,把它做成bean再序列化也不失為一種方便的選擇。
★Properties 在以前我總結(jié)的一篇關(guān)于集合框架的小文章里提到過,,Properties是歷史集合類的一個典型的例子,,這里主要不是介紹它的集合特性。大家可能都經(jīng)常接觸一些配置文件,,如Windows的ini文件,,Apache的conf文件,還有Java里的properties文件等,,這些文件當中的數(shù)據(jù)以“關(guān)鍵字-值”對的方式保存,。“環(huán)境變量”這個概念都知道吧,它也是一種“key-value”對,,以前也常??吹桨嫔蠁?#8220;如何取得系統(tǒng)某某信息”之類的問題,其實很多都保存在環(huán)境變量里,,只要用一條
就能獲得全部環(huán)境變量的列表:
-- listing properties -- java.runtime.name=Java(TM) 2 Runtime Environment, Stand... sun.boot.library.path=C:\Program Files\Java\j2re1.4.2_05\bin java.vm.version=1.4.2_05-b04 java.vm.vendor=Sun Microsystems Inc. java.vendor.url=http://java./ path.separator=; java.vm.name=Java HotSpot(TM) Client VM file.encoding.pkg=sun.io user.country=CN sun.os.patch.level=Service Pack 1 java.vm.specification.name=Java Virtual Machine Specification user.dir=d:\my documents\項目\eclipse\SWTDemo java.runtime.version=1.4.2_05-b04 java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment java.endorsed.dirs=C:\Program Files\Java\j2re1.4.2_05\li... os.arch=x86 java.io.tmpdir=C:\DOCUME~1\cn2lx0q0\LOCALS~1\Temp\ line.separator=
java.vm.specification.vendor=Sun Microsystems Inc. user.variant= os.name=Windows XP sun.java2d.fontpath= java.library.path=C:\Program Files\Java\j2re1.4.2_05\bi... java.specification.name=Java Platform API Specification java.class.version=48.0 java.util.prefs.PreferencesFactory=java.util.prefs.WindowsPreferencesFac... os.version=5.1 user.home=D:\Users\cn2lx0q0 user.timezone= java.awt.printerjob=sun.awt.windows.WPrinterJob file.encoding=GBK java.specification.version=1.4 user.name=cn2lx0q0 java.class.path=d:\my documents\項目\eclipse\SWTDemo\bi... java.vm.specification.version=1.0 sun.arch.data.model=32 java.home=C:\Program Files\Java\j2re1.4.2_05 java.specification.vendor=Sun Microsystems Inc. user.language=zh awt.toolkit=sun.awt.windows.WToolkit java.vm.info=mixed mode java.version=1.4.2_05 java.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./cgi-bin/bugreport... sun.cpu.endian=little sun.io.unicode.encoding=UnicodeLittle sun.cpu.isalist=pentium i486 i386
主要類與方法和描述
- load() //從一個外部流讀取屬性
- store() //將屬性保存到外部流(特別是文件)
- getProperty() //取得一個指定的屬性
- setProperty() //設(shè)置一個指定的屬性
- list() //列出這個Properties對象包含的全部“key-value”對
- System.getProperties() //取得系統(tǒng)當前的環(huán)境變量
你可以這樣保存一個properties文件:
- Properties prop = new Properties();
- prop.setProperty("key1", "value1");
- ...
- FileOutputStream out = new FileOutputStream("config.properties");
- prop.store(out, "--這里是文件頭,,可以加入注釋--");
★Preferences 如果我說Java里面可以不使用JNI的手段操作Windows的注冊表你信不信,?很多軟件的菜單里都有“Setting”或“Preferences”這樣的選項用來設(shè)定或修改軟件的配置,這些配置信息可以保存到一個像上面所述的配置文件當中,,如果是Windows平臺下,,也可能會保存到系統(tǒng)注冊表中。從JDK 1.4開始,,Java在java.util下加入了一個專門處理用戶和系統(tǒng)配置信息的java.util.prefs包,,其中一個類Preferences是一種比較“高級”的玩意。從本質(zhì)上講,,Preferences本身是一個與平臺無關(guān)的東西,,但不同的OS對它的SPI(Service Provider Interface)的實現(xiàn)卻是與平臺相關(guān)的,因此,,在不同的系統(tǒng)中你可能看到首選項保存為本地文件,、LDAP目錄項、數(shù)據(jù)庫條目等,,像在Windows平臺下,,它就保存到了系統(tǒng)注冊表中。不僅如此,,你還可以把首選項導出為XML文件或從XML文件導入,。
主要類與方法和描述
- systemNodeForPackage() //根據(jù)指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_LOCAL_MACHINE\”開始的
- systemRoot() //得到以注冊表路徑HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 為根結(jié)點的Preferences對象
- userNodeForPackage() //根據(jù)指定的Class對象得到一個Preferences對象,,這個對象的注冊表路徑是從“HKEY_CURRENT_USER\”開始的
- userRoot() //得到以注冊表路徑HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 為根結(jié)點的Preferences對象
- putXXX() //設(shè)置一個屬性的值,,這里XXX可以為基本數(shù)值型類型,如int,、long等,,但首字母大寫,表示參數(shù)為相應的類型,,也可以不寫而直接用put,,參數(shù)則為字符串
- getXXX() //得到一個屬性的值
- exportNode() //將全部首選項導出為一個XML文件
- exportSubtree() //將部分首選項導出為一個XML文件
- importPreferences() //從XML文件導入首選項
你可以按如下步驟保存數(shù)據(jù):
- 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\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個注冊表項
- Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\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);
網(wǎng)絡(luò)I/O:Socket→RMI
★Socket Socket編程可能大家都很熟,所以就不多討論了,,只是說通過socket把數(shù)據(jù)保存到遠端服務(wù)器或從網(wǎng)絡(luò)socket讀取數(shù)據(jù)也不失為一種值得考慮的方式,。
★RMI RMI機制其實就是RPC(遠程過程調(diào)用)的Java版本,它使用socket作為基本傳輸手段,,同時也是序列化最重要的一個應用?,F(xiàn)在網(wǎng)絡(luò)傳輸從編程的角度來看基本上都是以流的方式操作,socket就是一個例子,,將對象轉(zhuǎn)換成字節(jié)流的一個重要目標就是為了方便網(wǎng)絡(luò)傳輸,。
想象一下傳統(tǒng)的單機環(huán)境下的程序設(shè)計,對于Java語言的函數(shù)(方法)調(diào)用(注意與C語言函數(shù)調(diào)用的區(qū)別)的參數(shù)傳遞,,會有兩種情況:如果是基本數(shù)據(jù)類型,,這種情況下和C語言是一樣的,,采用值傳遞方式;如果是對象,,則傳遞的是對象的引用,,包括返回值也是引用,而不是一個完整的對象拷貝,!試想一下在不同的虛擬機之間進行方法調(diào)用,,即使是兩個完全同名同類型的對象他們也很可能是不同的引用!此外對于方法調(diào)用過程,,由于被調(diào)用過程的壓棧,,內(nèi)存“現(xiàn)場”完全被被調(diào)用者占有,當被調(diào)用方法返回時,,才將調(diào)用者的地址寫回到程序計數(shù)器(PC),,恢復調(diào)用者的狀態(tài),如果是兩個虛擬機,,根本不可能用簡單壓棧的方式來保存調(diào)用者的狀態(tài),。因為種種原因,我們才需要建立RMI通信實體之間的“代理”對象,,譬如“存根”就相當于遠程服務(wù)器對象在客戶機上的代理,,stub就是這么來的,當然這是后話了,。
本地對象與遠程對象(未必是物理位置上的不同機器,,只要不是在同一個虛擬機內(nèi)皆為“遠程”)之間傳遞參數(shù)和返回值,可能有這么幾種情形:
- 值傳遞:這又包括兩種子情形:如果是基本數(shù)據(jù)類型,,那么都是“可序列化”的,,統(tǒng)統(tǒng)序列化成可傳輸?shù)淖止?jié)流;如果是對象,,而且不是“遠程對象”(所謂“遠程對象”是實現(xiàn)了java.rmi.Remote接口的對象),本來對象傳遞的應該是引用,,但由于上述原因,,引用是不足以證明對象身份的,所以傳遞的仍然是一個序列化的拷貝(當然這個對象也必須滿足上述“可序列化”的條件),。
- 引用傳遞:可以引用傳遞的只能是“遠程對象”,。這里所謂的“引用”不要理解成了真的只是一個符號,它其實是一個留在(客戶機)本地stub中的,,和遠端服務(wù)器上那個真實的對象張得一模一樣的鏡像而已,!只是因為它有點“特權(quán)”(不需要經(jīng)過序列化),在本地內(nèi)存里已經(jīng)有了一個實例,,真正引用的其實是這個“孿生子”,。
由此可見,,序列化在RMI當中占有多么重要的地位。
數(shù)據(jù)庫I/O:CMP,、Hibernate
★什么是“Persistence” 用過VMWare的朋友大概都知道當一個guest OS正在運行的時候點擊“Suspend”將虛擬OS掛起,,它會把整個虛擬內(nèi)存的內(nèi)容保存到磁盤上,譬如你為虛擬OS分配了128M的運行內(nèi)存,,那掛起以后你會在虛擬OS所在的目錄下找到一個同樣是128M的文件,,這就是虛擬OS內(nèi)存的完整鏡像!這種內(nèi)存的鏡像手段其實就是“Persistence”(持久化)概念的由來,。
★CMP和Hibernate 因為我對J2EE的東西不是太熟悉,,隨便找了點材料看看,所以擔心說的不到位,,這次就不作具體總結(jié)了,,人要學習……真是一件痛苦的事情
序列化再探討
從以上技術(shù)的討論中我們不難體會到,序列化是Java之所以能夠出色地實現(xiàn)其鼓吹的兩大賣點??分布式(distributed)和跨平臺(OS independent)的一個重要基礎(chǔ),。TIJ(即“Thinking in Java”)談到I/O系統(tǒng)時,,把序列化稱為“lightweight persistence”??“輕量級的持久化”,這確實很有意思,。
★為什么叫做“序列”化,? 開場白里我說更習慣于把“Serialization”稱為“序列化”而不是“串行化”,這是有原因的,。介紹這個原因之前先回顧一些計算機基本的知識,,我們知道現(xiàn)代計算機的內(nèi)存空間都是線性編址的(什么是“線性”知道吧,就是一個元素只有一個唯一的“前驅(qū)”和唯一的“后繼”,,當然頭尾元素是個例外,;對于地址來說,它的下一個地址當然不可能有兩個,,否則就亂套了),,“地址”這個概念推廣到數(shù)據(jù)結(jié)構(gòu),就相當于“指針”,,這個在本科低年級大概就知道了,。注意了,既然是線性的,,那“地址”就可以看作是內(nèi)存空間的“序號”,,說明它的組織是有順序的,“序號”或者說“序列號”正是“Serialization”機制的一種體現(xiàn),。為什么這么說呢,?譬如我們有兩個對象a和b,分別是類A和B的實例,它們都是可序列化的,,而A和B都有一個類型為C的屬性,,根據(jù)前面我們說過的原則,,C當然也必須是可序列化的。
注意,,這里我們在實例化a和b的時候,,有意讓他們的c屬性使用同一個C類型對象的引用,譬如c1,,那么請試想一下,,但我們序列化a和b的時候,它們的c屬性在外部字節(jié)流(當然可以不僅僅是文件)里保存的是一份拷貝還是兩份拷貝呢,?序列化在這里使用的是一種類似于“指針”的方案:它為每個被序列化的對象標上一個“序列號”(serial number),,但序列化一個對象的時候,如果其某個屬性對象是已經(jīng)被序列化的,,那么這里只向輸出流寫入該屬性的序列號,;從字節(jié)流恢復被序列化的對象時,也根據(jù)序列號找到對應的流來恢復,。這就是“序列化”名稱的由來,!這里我們看到“序列化”和“指針”是極相似的,只不過“指針”是內(nèi)存空間的地址鏈,,而序列化用的是外部流中的“序列號鏈”,。 使用“序列號”而不是內(nèi)存地址來標識一個被序列化的對象,是因為從流中恢復對象到內(nèi)存,,其地址可能就未必是原來的地址了??我們需要的只是這些對象之間的引用關(guān)系,,而不是死板的原始位置,這在RMI中就更是必要,,在兩臺不同的機器之間傳遞對象(流),,根本就不可能指望它們在兩臺機器上都具有相同的內(nèi)存地址。
★更靈活的“序列化”:transient屬性和Externalizable Serializable確實很方便,,方便到你幾乎不需要做任何額外的工作就可以輕松將內(nèi)存中的對象保存到外部,。但有兩個問題使得Serializable的威力收到束縛: 一個是效率問題,《Core Java 2》中指出,,Serializable使用系統(tǒng)默認的序列化機制會影響軟件的運行速度,,因為需要為每個屬性的引用編號和查號,再加上I/O操作的時間(I/O和內(nèi)存讀寫差的可是一個數(shù)量級的大?。浯鷥r當然是可觀的,。
另一個困擾是“裸”的Serializable不可定制,,傻乎乎地什么都給你序列化了,不管你是不是想這么做,。其實你可以有至少三種定制序列化的選擇,。其中一種前面已經(jīng)提到了,,就是在implements Serializable的類里面添加私有的writeObject()和readObject()方法(這種Serializable就不裸了),在這兩個方法里,,該序列化什么,,不該序列化什么,那就由你說了算了,,你當然可以在這兩個方法體里面分別調(diào)用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執(zhí)行默認的序列化動作(那你在代碼上不就做無用功了,?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法對你中意的屬性進行序列化,。但虛擬機一看到你定義了這兩個方法,,它就不再用默認的機制了。
如果僅僅為了跳過某些屬性不讓它序列化,,上面的動作似乎顯得麻煩,,更簡單的方法是對不想序列化的屬性加上transient關(guān)鍵字,說明它是個“暫態(tài)變量”,,默認序列化的時候就不會把這些屬性也塞到外部流里了,。當然,你如果定義writeObject()和readObject()方法的化,,仍然可以把暫態(tài)變量進行序列化,。題外話,像transient,、violate,、finally這樣的關(guān)鍵字初學者可能會不太重視,而現(xiàn)在有的公司招聘就偏偏喜歡問這樣的問題 :(
再一個方案就是不實現(xiàn)Serializable而改成實現(xiàn)Externalizable接口,。我們研究一下這兩個接口的源代碼,,發(fā)現(xiàn)它們很類似,甚至容易混淆,。我們要記住的是:Externalizable默認并不保存任何對象相關(guān)信息,!任何保存和恢復對象的動作都是你自己定義的。Externalizable包含兩個public的方法:
- public void writeExternal(ObjectOutput out) throws IOException;
- public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
乍一看這和上面的writeObject()和readObject()幾乎差不多,,但Serializable和Externalizable走的是兩個不同的流程:Serializable在對象不存在的情況下,,就可以僅憑外部的字節(jié)序列把整個對象重建出來;但Externalizable在重建對象時,,先是調(diào)用該類的默認構(gòu)造函數(shù)(即不含參數(shù)的那個構(gòu)造函數(shù))使得內(nèi)存中先有這么一個實例,,然后再調(diào)用readExternal方法對實例中的屬性進行恢復,因此,,如果默認構(gòu)造函數(shù)中和readExternal方法中都沒有賦值的那些屬性,,特別他們是非基本類型的話,將會是空(null)。在這里需要注意的是,,transient只能用在對Serializable而不是Externalizable的實現(xiàn)里面,。
★序列化與克隆 從“可序列化”的遞歸定義來看,一個序列化的對象貌似對象內(nèi)存映象的外部克隆,,如果沒有共享引用的屬性的化,,那么應該是一個深度克隆。關(guān)于克隆的話題有可以談很多,,這里就不細說了,,有興趣的話可以參考IBM developerWorks上的一篇文章:JAVA中的指針,引用及對象的clone
一點啟示
作為一個實際的應用,我在寫那個簡易的郵件客戶端JExp的時候曾經(jīng)對比過好幾種保存Message對象(主要是幾個關(guān)鍵屬性和郵件的內(nèi)容)到本地的方法,,譬如XML,、Properties等,最后還是選擇了用序列化的方式,,因為這種方法最簡單,, 大約可算是“學以致用”罷。這里“存取程序狀態(tài)”其實只是一個引子話題罷了,,我想說的是:就如同前面我們討論的關(guān)于logging的話題一樣,在Java面前對同一個問題你可以有很多種solution:熟悉文件操作的,,你可能會覺得Properties、XML或Bean比較方便,,然后又發(fā)現(xiàn)了還有Preferences這么一個東東,,大概又會感慨“天外有天”了,等到你接觸了很多種新方法以后,,結(jié)果又會“殊途同歸”,,重新反省Serialization機制本身。這不僅是Java,,科學也是同樣的道理,。 |
|
|