欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
存取程序狀態(tài)的幾種方法--Java I/O應用雜談
今天稍微聊一點(diǎn)關(guān)于“程序狀態(tài)保存”方面的問(wèn)題,我們很容易就會(huì )想到“序列化”(Serialization,有的書(shū)上又翻譯為“順序化”或者“串行化”,但“串行”一詞總是讓我聯(lián)想到通信和硬件接口,所以我更習慣于“序列化”的叫法,何況這種叫法是有來(lái)頭的,后面我會(huì )談到這個(gè)名稱(chēng)的由來(lái)),當然,序列化是一種方便有效的數據存取方式,但它還有更加廣泛的應用。廣義上講,就是討論一下I/O的一些應用。 

文件I/O:文件流→序列化


文件流
    文件操作是最簡(jiǎn)單最直接也是最容易想到的一種方式,我們說(shuō)的文件操作不僅僅是通過(guò)FileInputStream/FileOutputStream這么“裸”的方式直接把數據寫(xiě)入到本地文件(像我以前寫(xiě)的一個(gè)掃雷的小游戲JavaMine就是這樣保存一局的狀態(tài)的),這樣就比較“底層”了。 
 
主要類(lèi)與方法和描述 
  1. FileInputStream.read() //從本地文件讀取二進(jìn)制格式的數據 
  2. FileReader.read() //從本地文件讀取字符(文本)數據 
  3. FileOutputStream.write() //保存二進(jìn)制數據到本地文件 
  4. FileWriter.write() //保存字符數據到本地文件
 
XML
    和上面的單純的I/O方式相比,XML就顯得“高檔”得多,以至于成為一種數據交換的標準。以DOM方式為例,它關(guān)心的是首先在內存中構造文檔樹(shù),數據保存在某個(gè)結點(diǎn)上(可以是葉子結點(diǎn),也可以是標簽結點(diǎn)的屬性),構造好了以后一次性的寫(xiě)入到外部文件,但我們只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多數人也實(shí)踐過(guò),所以這里也只列出相關(guān)的方法,供初學(xué)者預先了解一下。主要的包是javax.xml.parsers,org.w3c.dom,javax.xml.transform。 

主要類(lèi)與方法和描述 
  1. DocumentBuilderFactory.newDocumentBuilder().parse() //解析一個(gè)外部的XML文件,得到一個(gè)Document對象的DOM樹(shù) 
  2. DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() //初始化一棵DOM樹(shù) 
  3. Document.getDocumentElement().appendChild() //為一個(gè)標簽結點(diǎn)添加一個(gè)子結點(diǎn) 
  4. Document.createTextNode() //生成一個(gè)字符串結點(diǎn) 
  5. Node.getChildNodes() //取得某個(gè)結點(diǎn)的所有下一層子結點(diǎn) 
  6. Node.removeChild()  //刪除某個(gè)結點(diǎn)的子結點(diǎn) 
  7. Document.getElementsByTagName() 查找所有指定名稱(chēng)的標簽結點(diǎn) 
  8. Document.getElementById() //查找指定名稱(chēng)的一個(gè)標簽結點(diǎn),如果有多個(gè)符合,則返回某一個(gè),通常是第一個(gè) 
  9. Element.getAttribute() //取得一個(gè)標簽的某個(gè)屬性的的值 
  10. Element.setAttribute() //設置一個(gè)標簽的某個(gè)屬性的的值 
  11. Element.removeAttribute() //刪除一個(gè)標簽的某個(gè)屬性 
  12. TransformerFactory.newInstance().newTransformer().transform() //將一棵DOM樹(shù)寫(xiě)入到外部XML文件

序列化
    使用基本的文件讀寫(xiě)方式存取數據,如果我們僅僅保存相同類(lèi)型的數據,則可以用同一種格式保存,譬如在我的JavaMine中保存一個(gè)盤(pán)局時(shí),需要保存每一個(gè)方格的坐標、是否有地雷,是否被翻開(kāi)等,這些信息組合成一個(gè)“復合類(lèi)型”;相反,如果有多種不同類(lèi)型的數據,那我們要么把它分解成若干部分,以相同類(lèi)型(譬如String)保存,要么我們需要在程序中添加解析不同類(lèi)型數據格式的邏輯,這就很不方便。于是我們期望用一種比較“高”的層次上處理數據,程序員應該花盡可能少的時(shí)間和代碼對數據進(jìn)行解析,事實(shí)上,序列化操作為我們提供了這樣一條途徑。
    序列化(Serialization)大家可能都有所接觸,它可以把對象以某種特定的編碼格式寫(xiě)入或從外部字節流(即ObjectInputStream/ObjectOutputStream)中讀取。序列化一個(gè)對象非常之簡(jiǎn)單,僅僅實(shí)現一下Serializable接口即可,甚至都不用為它專(zhuān)門(mén)添加任何方法: 
  1. public class MySerial implements java.io.Serializable
  2. {
  3.   //...
  4. }
 
但有一個(gè)條件:即你要序列化的類(lèi)當中,它的每個(gè)屬性都必須是是“可序列化”的。這句話(huà)說(shuō)起來(lái)有點(diǎn)拗口,其實(shí)所有基本類(lèi)型(就是int,char,boolean之類(lèi)的)都是“可序列化”的,而你可以看看JDK文檔,會(huì )發(fā)現很多類(lèi)其實(shí)已經(jīng)實(shí)現了Serializable(即已經(jīng)是“可序列化”的了),于是這些類(lèi)的對象以及基本數據類(lèi)型都可以直接作為你需要序列化的那個(gè)類(lèi)的內部屬性。如果碰到了不是“可序列化”的屬性怎么辦?對不起,那這個(gè)屬性的類(lèi)還需要事先實(shí)現Serializable接口,如此遞歸,直到所有屬性都是“可序列化”的。 

主要類(lèi)與方法和描述 
  1. ObjectOutputStream.writeObject() //將一個(gè)對象序列化到外部字節流 
  2. ObjectInputStream.readObject() //從外部字節流讀取并重新構造對象
 
    從實(shí)際應用上看來(lái),“Serializable”這個(gè)接口并沒(méi)有定義任何方法,仿佛它只是一個(gè)標記(或者說(shuō)像是Java的關(guān)鍵字)而已,一旦虛擬機看到這個(gè)“標記”,就會(huì )嘗試調用自身預定義的序列化機制,除非你在實(shí)現Serializable接口的同時(shí)還定義了私有的readObject()或writeObject()方法。這一點(diǎn)很奇怪。不過(guò)你要是不愿意讓系統使用缺省的方式進(jìn)行序列化,那就必須定義上面提到的兩個(gè)方法: 
  1. public class MySerial implements java.io.Serializable
  2. {
  3.   private void writeObject(java.io.ObjectOutputStream out) throws IOException
  4.   {
  5.     //...
  6.   }
  7.   private void readObject(java.io.ObjectInputStream in) throws IOExceptionClassNotFoundException
  8.   {
  9.     //...
  10.   }
  11.   //...

    譬如你可以在上面的writeObject()里調用默認的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意將某些敏感的屬性和信息序列化,你也可以調用ObjectOutputStream.writeObject()方法明確指定需要序列化那些屬性。關(guān)于用戶(hù)可定制的序列化方法,我們將在后面提到。 

Bean
    上面的序列化只是一種基本應用,你把一個(gè)對象序列化到外部文件以后,用notepad打開(kāi)那個(gè)文件,只能從為數不多的一些可讀字符中猜到這是有關(guān)這個(gè)類(lèi)的信息文件,這需要你熟悉序列化文件的字節編碼方式,那將是比較痛苦的(在《Core Java 2》第一卷里提到了相關(guān)編碼方式,有興趣的話(huà)可以查看參考資料),某些情況下我們可能需要被序列化的文件具有更好的可讀性。另一方面,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開(kāi)始,其規范里也要求支持文本方式的“長(cháng)期的持久化”(long-term persistence)。

    打開(kāi)JDK文檔,java.beans包里的有一個(gè)名為“Encoder”的類(lèi),這就是一個(gè)可以序列化bean的實(shí)用類(lèi)。和它相關(guān)的兩個(gè)主要類(lèi)有XMLEcoder和XMLDecoder,顯然,這是以XML文件的格式保存和讀取bean的工具。他們的用法也很簡(jiǎn)單,和上面ObjectOutputStream/ObjectInputStream比較類(lèi)似。 

主要類(lèi)與方法和描述 
  1. XMLEncoder.writeObject() //將一個(gè)對象序列化到外部字節流 
  2. XMLDecoder.readObject() //從外部字節流讀取并重新構造對象 

    如果一個(gè)bean是如下格式:
  1. public class MyBean
  2. {
  3.   int i;
  4.   char[] c;
  5.   String s;
  6.   //...(get和set操作省略)...

那么通過(guò)XMLEcoder序列化出來(lái)的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> 

    像AWTSwing中很多可視化組件都是bean,當然也是可以用這種方式序列化的,下面就是從JDK文檔中摘錄的一個(gè)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> 

    因此但你想要保存的數據是一些不是太復雜的類(lèi)型的話(huà),把它做成bean再序列化也不失為一種方便的選擇。 

Properties
    在以前我總結的一篇關(guān)于集合框架的小文章里提到過(guò),Properties是歷史集合類(lèi)的一個(gè)典型的例子,這里主要不是介紹它的集合特性。大家可能都經(jīng)常接觸一些配置文件,如Windows的ini文件,Apache的conf文件,還有Java里的properties文件等,這些文件當中的數據以“關(guān)鍵字-值”對的方式保存。“環(huán)境變量”這個(gè)概念都知道吧,它也是一種“key-value”對,以前也常??吹桨嫔蠁?wèn)“如何取得系統某某信息”之類(lèi)的問(wèn)題,其實(shí)很多都保存在環(huán)境變量里,只要用一條
  1. System.getProperties().list(System.out); 

就能獲得全部環(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.sun.com/
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.sun.com/cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.isalist=pentium i486 i386
 

主要類(lèi)與方法和描述 
  1. load() //從一個(gè)外部流讀取屬性 
  2. store() //將屬性保存到外部流(特別是文件) 
  3. getProperty() //取得一個(gè)指定的屬性 
  4. setProperty() //設置一個(gè)指定的屬性 
  5. list() //列出這個(gè)Properties對象包含的全部“key-value”對 
  6. System.getProperties() //取得系統當前的環(huán)境變量 


    你可以這樣保存一個(gè)properties文件: 

  1. Properties prop = new Properties();
  2. prop.setProperty("key1""value1");
  3. ...
  4. FileOutputStream out = new FileOutputStream("config.properties");
  5. prop.store(out, "--這里是文件頭,可以加入注釋--"); 

Preferences
    如果我說(shuō)Java里面可以不使用JNI的手段操作Windows的注冊表你信不信?很多軟件的菜單里都有“Setting”或“Preferences”這樣的選項用來(lái)設定或修改軟件的配置,這些配置信息可以保存到一個(gè)像上面所述的配置文件當中,如果是Windows平臺下,也可能會(huì )保存到系統注冊表中。從JDK 1.4開(kāi)始,Java在java.util下加入了一個(gè)專(zhuān)門(mén)處理用戶(hù)和系統配置信息的java.util.prefs包,其中一個(gè)類(lèi)Preferences是一種比較“高級”的玩意。從本質(zhì)上講,Preferences本身是一個(gè)與平臺無(wú)關(guān)的東西,但不同的OS對它的SPI(Service Provider Interface)的實(shí)現卻是與平臺相關(guān)的,因此,在不同的系統中你可能看到首選項保存為本地文件、LDAP目錄項、數據庫條目等,像在Windows平臺下,它就保存到了系統注冊表中。不僅如此,你還可以把首選項導出為XML文件或從XML文件導入。 

主要類(lèi)與方法和描述 
  1. systemNodeForPackage() //根據指定的Class對象得到一個(gè)Preferences對象,這個(gè)對象的注冊表路徑是從“HKEY_LOCAL_MACHINE\”開(kāi)始的 
  2. systemRoot() //得到以注冊表路徑HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 為根結點(diǎn)的Preferences對象 
  3. userNodeForPackage() //根據指定的Class對象得到一個(gè)Preferences對象,這個(gè)對象的注冊表路徑是從“HKEY_CURRENT_USER\”開(kāi)始的 
  4. userRoot() //得到以注冊表路徑HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 為根結點(diǎn)的Preferences對象 
  5. putXXX() //設置一個(gè)屬性的值,這里XXX可以為基本數值型類(lèi)型,如int、long等,但首字母大寫(xiě),表示參數為相應的類(lèi)型,也可以不寫(xiě)而直接用put,參數則為字符串 
  6. getXXX() //得到一個(gè)屬性的值 
  7. exportNode() //將全部首選項導出為一個(gè)XML文件 
  8. exportSubtree() //將部分首選項導出為一個(gè)XML文件 
  9. importPreferences() //從XML文件導入首選項 

    你可以按如下步驟保存數據:
  1. Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 這種方法是在“HKEY_CURRENT_USER\”下按當前類(lèi)的路徑建立一個(gè)注冊表項
  2. Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 這種方法是在“HKEY_LOCAL_MACHINE\”下按當前類(lèi)的路徑建立一個(gè)注冊表項
  3. Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個(gè)注冊表項
  4. Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個(gè)注冊表項
  5. myPrefs1.putInt("key1", 10);
  6. myPrefs1.putDouble("key2", -7.15);
  7. myPrefs1.put("key3""value3");
  8. FileOutputStream out = new FileOutputStream("prefs.xml");
  9. myPrefs1.exportNode(out);

網(wǎng)絡(luò )I/O:Socket→RMI


Socket
    Socket編程可能大家都很熟,所以就不多討論了,只是說(shuō)通過(guò)socket把數據保存到遠端服務(wù)器或從網(wǎng)絡(luò )socket讀取數據也不失為一種值得考慮的方式。

RMI
    RMI機制其實(shí)就是RPC(遠程過(guò)程調用)的Java版本,它使用socket作為基本傳輸手段,同時(shí)也是序列化最重要的一個(gè)應用?,F在網(wǎng)絡(luò )傳輸從編程的角度來(lái)看基本上都是以流的方式操作,socket就是一個(gè)例子,將對象轉換成字節流的一個(gè)重要目標就是為了方便網(wǎng)絡(luò )傳輸。

    想象一下傳統的單機環(huán)境下的程序設計,對于Java語(yǔ)言的函數(方法)調用(注意與C語(yǔ)言函數調用的區別)的參數傳遞,會(huì )有兩種情況:如果是基本數據類(lèi)型,這種情況下和C語(yǔ)言是一樣的,采用值傳遞方式;如果是對象,則傳遞的是對象的引用,包括返回值也是引用,而不是一個(gè)完整的對象拷貝!試想一下在不同的虛擬機之間進(jìn)行方法調用,即使是兩個(gè)完全同名同類(lèi)型的對象他們也很可能是不同的引用!此外對于方法調用過(guò)程,由于被調用過(guò)程的壓棧,內存“現場(chǎng)”完全被被調用者占有,當被調用方法返回時(shí),才將調用者的地址寫(xiě)回到程序計數器(PC),恢復調用者的狀態(tài),如果是兩個(gè)虛擬機,根本不可能用簡(jiǎn)單壓棧的方式來(lái)保存調用者的狀態(tài)。因為種種原因,我們才需要建立RMI通信實(shí)體之間的“代理”對象,譬如“存根”就相當于遠程服務(wù)器對象在客戶(hù)機上的代理,stub就是這么來(lái)的,當然這是后話(huà)了。

    本地對象與遠程對象(未必是物理位置上的不同機器,只要不是在同一個(gè)虛擬機內皆為“遠程”)之間傳遞參數和返回值,可能有這么幾種情形:
  • 值傳遞:這又包括兩種子情形:如果是基本數據類(lèi)型,那么都是“可序列化”的,統統序列化成可傳輸的字節流;如果是對象,而且不是“遠程對象”(所謂“遠程對象”是實(shí)現了java.rmi.Remote接口的對象),本來(lái)對象傳遞的應該是引用,但由于上述原因,引用是不足以證明對象身份的,所以傳遞的仍然是一個(gè)序列化的拷貝(當然這個(gè)對象也必須滿(mǎn)足上述“可序列化”的條件)。 
  • 引用傳遞:可以引用傳遞的只能是“遠程對象”。這里所謂的“引用”不要理解成了真的只是一個(gè)符號,它其實(shí)是一個(gè)留在(客戶(hù)機)本地stub中的,和遠端服務(wù)器上那個(gè)真實(shí)的對象張得一模一樣的鏡像而已!只是因為它有點(diǎn)“特權”(不需要經(jīng)過(guò)序列化),在本地內存里已經(jīng)有了一個(gè)實(shí)例,真正引用的其實(shí)是這個(gè)“孿生子”。
 
    由此可見(jiàn),序列化在RMI當中占有多么重要的地位。

數據庫I/O:CMP、Hibernate


什么是“Persistence”
    用過(guò)VMWare的朋友大概都知道當一個(gè)guest OS正在運行的時(shí)候點(diǎn)擊“Suspend”將虛擬OS掛起,它會(huì )把整個(gè)虛擬內存的內容保存到磁盤(pán)上,譬如你為虛擬OS分配了128M的運行內存,那掛起以后你會(huì )在虛擬OS所在的目錄下找到一個(gè)同樣是128M的文件,這就是虛擬OS內存的完整鏡像!這種內存的鏡像手段其實(shí)就是“Persistence”(持久化)概念的由來(lái)。

CMP和Hibernate
    因為我對J2EE的東西不是太熟悉,隨便找了點(diǎn)材料看看,所以擔心說(shuō)的不到位,這次就不作具體總結了,人要學(xué)習……真是一件痛苦的事情

序列化再探討


    從以上技術(shù)的討論中我們不難體會(huì )到,序列化是Java之所以能夠出色地實(shí)現其鼓吹的兩大賣(mài)點(diǎn)??分布式(distributed)和跨平臺(OS independent)的一個(gè)重要基礎。TIJ(即“Thinking in Java”)談到I/O系統時(shí),把序列化稱(chēng)為“lightweight persistence”??“輕量級的持久化”,這確實(shí)很有意思。

為什么叫做“序列”化?
    開(kāi)場(chǎng)白里我說(shuō)更習慣于把“Serialization”稱(chēng)為“序列化”而不是“串行化”,這是有原因的。介紹這個(gè)原因之前先回顧一些計算機基本的知識,我們知道現代計算機的內存空間都是線(xiàn)性編址的(什么是“線(xiàn)性”知道吧,就是一個(gè)元素只有一個(gè)唯一的“前驅”和唯一的“后繼”,當然頭尾元素是個(gè)例外;對于地址來(lái)說(shuō),它的下一個(gè)地址當然不可能有兩個(gè),否則就亂套了),“地址”這個(gè)概念推廣到數據結構,就相當于“指針”,這個(gè)在本科低年級大概就知道了。注意了,既然是線(xiàn)性的,那“地址”就可以看作是內存空間的“序號”,說(shuō)明它的組織是有順序的,“序號”或者說(shuō)“序列號”正是“Serialization”機制的一種體現。為什么這么說(shuō)呢?譬如我們有兩個(gè)對象a和b,分別是類(lèi)A和B的實(shí)例,它們都是可序列化的,而A和B都有一個(gè)類(lèi)型為C的屬性,根據前面我們說(shuō)過(guò)的原則,C當然也必須是可序列化的。
  1. import java.io.*;
  2. ...
  3. class A implements Serializable
  4. {
  5.   C c;
  6.   ...
  7. }
  8.  
  9. class B implements Serializable
  10. {
  11.   C c;
  12.   ...
  13. }
  14.  
  15. class C implements Serializable
  16. {
  17.   ...
  18. }
  19.  
  20. A a;
  21. B b;
  22. C c1;
  23. ...

    注意,這里我們在實(shí)例化a和b的時(shí)候,有意讓他們的c屬性使用同一個(gè)C類(lèi)型對象的引用,譬如c1,那么請試想一下,但我們序列化a和b的時(shí)候,它們的c屬性在外部字節流(當然可以不僅僅是文件)里保存的是一份拷貝還是兩份拷貝呢?序列化在這里使用的是一種類(lèi)似于“指針”的方案:它為每個(gè)被序列化的對象標上一個(gè)“序列號”(serial number),但序列化一個(gè)對象的時(shí)候,如果其某個(gè)屬性對象是已經(jīng)被序列化的,那么這里只向輸出流寫(xiě)入該屬性的序列號;從字節流恢復被序列化的對象時(shí),也根據序列號找到對應的流來(lái)恢復。這就是“序列化”名稱(chēng)的由來(lái)!這里我們看到“序列化”和“指針”是極相似的,只不過(guò)“指針”是內存空間的地址鏈,而序列化用的是外部流中的“序列號鏈”。
    使用“序列號”而不是內存地址來(lái)標識一個(gè)被序列化的對象,是因為從流中恢復對象到內存,其地址可能就未必是原來(lái)的地址了??我們需要的只是這些對象之間的引用關(guān)系,而不是死板的原始位置,這在RMI中就更是必要,在兩臺不同的機器之間傳遞對象(流),根本就不可能指望它們在兩臺機器上都具有相同的內存地址。 

更靈活的“序列化”:transient屬性和Externalizable
    Serializable確實(shí)很方便,方便到你幾乎不需要做任何額外的工作就可以輕松將內存中的對象保存到外部。但有兩個(gè)問(wèn)題使得Serializable的威力收到束縛:
    一個(gè)是效率問(wèn)題,《Core Java 2》中指出,Serializable使用系統默認的序列化機制會(huì )影響軟件的運行速度,因為需要為每個(gè)屬性的引用編號和查號,再加上I/O操作的時(shí)間(I/O和內存讀寫(xiě)差的可是一個(gè)數量級的大?。?,其代價(jià)當然是可觀(guān)的。

    另一個(gè)困擾是“裸”的Serializable不可定制,傻乎乎地什么都給你序列化了,不管你是不是想這么做。其實(shí)你可以有至少三種定制序列化的選擇。其中一種前面已經(jīng)提到了,就是在implements Serializable的類(lèi)里面添加私有的writeObject()和readObject()方法(這種Serializable就不裸了),在這兩個(gè)方法里,該序列化什么,不該序列化什么,那就由你說(shuō)了算了,你當然可以在這兩個(gè)方法體里面分別調用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執行默認的序列化動(dòng)作(那你在代碼上不就做無(wú)用功了?呵呵),也可以用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()方法對你中意的屬性進(jìn)行序列化。但虛擬機一看到你定義了這兩個(gè)方法,它就不再用默認的機制了。

    如果僅僅為了跳過(guò)某些屬性不讓它序列化,上面的動(dòng)作似乎顯得麻煩,更簡(jiǎn)單的方法是對不想序列化的屬性加上transient關(guān)鍵字,說(shuō)明它是個(gè)“暫態(tài)變量”,默認序列化的時(shí)候就不會(huì )把這些屬性也塞到外部流里了。當然,你如果定義writeObject()和readObject()方法的化,仍然可以把暫態(tài)變量進(jìn)行序列化。題外話(huà),像transient、violate、finally這樣的關(guān)鍵字初學(xué)者可能會(huì )不太重視,而現在有的公司招聘就偏偏喜歡問(wèn)這樣的問(wèn)題 :(

    再一個(gè)方案就是不實(shí)現Serializable而改成實(shí)現Externalizable接口。我們研究一下這兩個(gè)接口的源代碼,發(fā)現它們很類(lèi)似,甚至容易混淆。我們要記住的是:Externalizable默認并不保存任何對象相關(guān)信息!任何保存和恢復對象的動(dòng)作都是你自己定義的。Externalizable包含兩個(gè)public的方法:
  1. public void writeExternal(ObjectOutput out) throws IOException;
  2. public void readExternal(ObjectInput in) throws IOExceptionClassNotFoundException;

    乍一看這和上面的writeObject()和readObject()幾乎差不多,但Serializable和Externalizable走的是兩個(gè)不同的流程:Serializable在對象不存在的情況下,就可以?xún)H憑外部的字節序列把整個(gè)對象重建出來(lái);但Externalizable在重建對象時(shí),先是調用該類(lèi)的默認構造函數(即不含參數的那個(gè)構造函數)使得內存中先有這么一個(gè)實(shí)例,然后再調用readExternal方法對實(shí)例中的屬性進(jìn)行恢復,因此,如果默認構造函數中和readExternal方法中都沒(méi)有賦值的那些屬性,特別他們是非基本類(lèi)型的話(huà),將會(huì )是空(null)。在這里需要注意的是,transient只能用在對Serializable而不是Externalizable的實(shí)現里面。 

序列化與克隆
    從“可序列化”的遞歸定義來(lái)看,一個(gè)序列化的對象貌似對象內存映象的外部克隆,如果沒(méi)有共享引用的屬性的化,那么應該是一個(gè)深度克隆。關(guān)于克隆的話(huà)題有可以談很多,這里就不細說(shuō)了,有興趣的話(huà)可以參考IBM developerWorks上的一篇文章:JAVA中的指針,引用及對象的clone

一點(diǎn)啟示

    作為一個(gè)實(shí)際的應用,我在寫(xiě)那個(gè)簡(jiǎn)易的郵件客戶(hù)端JExp的時(shí)候曾經(jīng)對比過(guò)好幾種保存Message對象(主要是幾個(gè)關(guān)鍵屬性和郵件的內容)到本地的方法,譬如XML、Properties等,最后還是選擇了用序列化的方式,因為這種方法最簡(jiǎn)單, 大約可算是“學(xué)以致用”罷。這里“存取程序狀態(tài)”其實(shí)只是一個(gè)引子話(huà)題罷了,我想說(shuō)的是:就如同前面我們討論的關(guān)于logging的話(huà)題一樣,在Java面前對同一個(gè)問(wèn)題你可以有很多種solution:熟悉文件操作的,你可能會(huì )覺(jué)得Properties、XML或Bean比較方便,然后又發(fā)現了還有Preferences這么一個(gè)東東,大概又會(huì )感慨“天外有天”了,等到你接觸了很多種新方法以后,結果又會(huì )“殊途同歸”,重新反省Serialization機制本身。這不僅是Java,科學(xué)也是同樣的道理。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
電腦操作的技巧與竅門(mén)(包括組合鍵,設定技巧等)超全面!
隱藏文件夾的7種方法
電腦運行速度慢的原因及解決方法
如何讓系統速度如飛
十四種系統故障的解決方法
Windows變慢原因分析及解決方法 軟件篇
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久