<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 56,  comments - 12,  trackbacks - 0
    jungleford如是說

         已經(jīng)有一個(gè)多月沒有搭理blog了,原因很多,譬如實(shí)驗(yàn)室的項(xiàng)目正在收工,巨忙;譬如找工作及其相關(guān)的事情;而且二月份大部分時(shí)間是陪老爹老媽,家里撥號(hào) 的速度可想而知……但主要還是沒有找到一個(gè)合適的topic,或者說這段時(shí)間懶了(臨畢業(yè)前期綜合癥),凈在看《漢武大帝》和歷史方面的書,還有其它亂七 八糟的閑書,就是沒有認(rèn)真地玩Java,哈哈!現(xiàn)在工作差不多落實(shí)了,好在不算太爛,小資青年jungleford的生活又開始步入正軌了!以上是新年里 的一些廢話。
        今天稍微聊一點(diǎn)關(guān)于“程序狀態(tài)保存”方面的問題,我們很容易就會(huì)想到“序列化”(Serialization,有的書上又翻譯為“順序化”或者“串行化”,但“串行”一詞總是讓我聯(lián)想到通信和硬件接口,所以我更習(xí)慣于“序列化”的叫法,何況這種叫法是有來頭的,后面我會(huì)談到這個(gè)名稱的由來),當(dāng)然,序列化是一種方便有效的數(shù)據(jù)存取方式,但它還有更加廣泛的應(yīng)用。廣義上講,就是討論一下I/O的一些應(yīng)用。

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

    文件流
        文件操作是最簡(jiǎn)單最直接也是最容易想到的一種方式,我們說的文件操作不僅僅是通過FileInputStream/FileOutputStream這么“裸”的方式直接把數(shù)據(jù)寫入到本地文件(像我以前寫的一個(gè)掃雷的小游戲JavaMine就是這樣保存一局的狀態(tài)的),這樣就比較“底層”了。

    主要類與方法 描述
    FileInputStream .read() 從本地文件讀取二進(jìn)制格式的數(shù)據(jù)
    FileReader .read() 從本地文件讀取字符(文本)數(shù)據(jù)
    FileOutputStream .write() 保存二進(jìn)制數(shù)據(jù)到本地文件
    FileWriter .write() 保存字符數(shù)據(jù)到本地文件

    XML
        和上面的單純的I/O方式相比,XML就顯得“高檔”得多,以至于成為一種數(shù)據(jù)交換的標(biāo)準(zhǔn)。以DOM方式為例,它關(guān)心的是首先在內(nèi)存中構(gòu)造文檔樹,數(shù)據(jù)保存在某個(gè)結(jié)點(diǎn)上(可以是葉子結(jié)點(diǎn),也可以是標(biāo)簽結(jié)點(diǎn)的屬性),構(gòu)造好了以后一次性的寫入到外部文件,但我們只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多數(shù)人也實(shí)踐過,所以這里也只列出相關(guān)的方法,供初學(xué)者預(yù)先了解一下。主要的包是javax.xml.parsersorg.w3c.domjavax.xml.transform

    主要類與方法 描述
    DocumentBuilderFactory .newDocumentBuilder().parse() 解析一個(gè)外部的XML文件,得到一個(gè)Document對(duì)象的DOM樹
    DocumentBuilderFactory .newInstance().newDocumentBuilder().newDocument() 初始化一棵DOM樹
    Document .getDocumentElement(). appendChild() 為一個(gè)標(biāo)簽結(jié)點(diǎn)添加一個(gè)子結(jié)點(diǎn)
    Document .createTextNode() 生成一個(gè)字符串結(jié)點(diǎn)
    Node .getChildNodes() 取得某個(gè)結(jié)點(diǎn)的所有下一層子結(jié)點(diǎn)
    Node .removeChild() 刪除某個(gè)結(jié)點(diǎn)的子結(jié)點(diǎn)
    Document . getElementsByTagName() 查找所有指定名稱的標(biāo)簽結(jié)點(diǎn)
    Document .getElementById() 查找指定名稱的一個(gè)標(biāo)簽結(jié)點(diǎn),如果有多個(gè)符合,則返回某一個(gè),通常是第一個(gè)
    Element .getAttribute() 取得一個(gè)標(biāo)簽的某個(gè)屬性的的值
    Element .setAttribute() 設(shè)置一個(gè)標(biāo)簽的某個(gè)屬性的的值
    Element .removeAttribute() 刪除一個(gè)標(biāo)簽的某個(gè)屬性
    TransformerFactory .newInstance().newTransformer().transform() 將一棵DOM樹寫入到外部XML文件

    序列化
        使用基本的文件讀寫方式存取數(shù)據(jù),如果我們僅僅保存相同類型的數(shù)據(jù),則可以用同一種格式保存,譬如在我的JavaMine中保存一個(gè)盤局時(shí),需要保存每一個(gè)方格的坐標(biāo)、是否有地雷,是否被翻開等,這些信息組合成一個(gè)“復(fù)合類型”;相反,如果有多種不同類型的數(shù)據(jù),那我們要么把它分解成若干部分,以相同類型(譬如String)保存,要么我們需要在程序中添加解析不同類型數(shù)據(jù)格式的邏輯,這就很不方便。于是我們期望用一種比較“高”的層次上處理數(shù)據(jù),程序員應(yīng)該花盡可能少的時(shí)間和代碼對(duì)數(shù)據(jù)進(jìn)行解析,事實(shí)上,序列化操作為我們提供了這樣一條途徑。
        序列化(Serialization)大家可能都有所接觸,它可以把對(duì)象以某種特定的編碼格式寫入或從外部字節(jié)流(即ObjectInputStream/ObjectOutputStream)中讀取。序列化一個(gè)對(duì)象非常之簡(jiǎn)單,僅僅實(shí)現(xiàn)一下Serializable接口即可,甚至都不用為它專門添加任何方法:

    public class MySerial implements java.io.Serializable
    {
      ...
    }

    但有一個(gè)條件:即, 會(huì)發(fā)現(xiàn)很多類其實(shí)已經(jīng)實(shí)現(xiàn)了Serializable(即已經(jīng)是“可序列化”的了),于是這些類的對(duì)象以及基本數(shù)據(jù)類型都可以直接作為你需要序列化的那個(gè) 類的內(nèi)部屬性。如果碰到了不是“可序列化”的屬性怎么辦?對(duì)不起,那這個(gè)屬性的類還需要事先實(shí)現(xiàn)Serializable接口,如此遞歸,直到所有屬性都是“可序列化”的

    主要類與方法 描述
    ObjectOutputStream .writeObject() 將一個(gè)對(duì)象序列化到外部字節(jié)流
    ObjectInputStream .readObject() 從外部字節(jié)流讀取并重新構(gòu)造對(duì)象

        從實(shí)際應(yīng)用上看來,“Serializable”這個(gè)接口并沒有定義任何方法,仿佛它只是一個(gè)標(biāo)記(或者說像是Java的關(guān)鍵字)而已,一旦虛擬機(jī)看到這個(gè)“標(biāo)記”,就會(huì)嘗試調(diào)用自身預(yù)定義的序列化機(jī)制,除非你在實(shí)現(xiàn)Serializable接口的同時(shí)還定義了私有的readObject()或writeObject()方法。這一點(diǎn)很奇怪。不過你要是不愿意讓系統(tǒng)使用缺省的方式進(jìn)行序列化,那就必須定義上面提到的兩個(gè)方法:

    public class MySerial implements java.io.Serializable
    {
      private void writeObject(java.io.ObjectOutputStream out) throwsIOException
      {
        ...
      }
      private void readObject(java.io.ObjectInputStream in) throwsIOException, ClassNotFoundException
      {
        ...
      }
      ...
    }

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

    Bean
        上面的序列化只是一種基本應(yīng)用,你把一個(gè)對(duì)象序列化到外部文件以后,用notepad打開那個(gè)文件,只能從為數(shù)不多的一些可讀字符中猜到這是有關(guān)這個(gè)類的信息文件,這需要你熟悉序列化文件的字節(jié)編碼方式,那將是比較痛苦的(在《Core Java 2》第一卷里提到了相關(guān)編碼方式,有興趣的話可以查看參考資料),某些情況下我們可能需要被序列化的文件具有更好的可讀性。另一方面,作為Java組件的核心概念“JavaBeans”,從JDK 1.4開始,其規(guī)范里也要求支持文本方式的“長期的持久化”(long-term persistence)。
        打開JDK文檔java.beans包里的有一個(gè)名為“Encoder”的類,這就是一個(gè)可以序列化bean的實(shí)用類。和它相關(guān)的兩個(gè)主要類有XMLEcoderXMLDecoder,顯然,這是以XML文件的格式保存和讀取bean的工具。他們的用法也很簡(jiǎn)單,和上面ObjectOutputStream/ObjectInputStream比較類似。

    主要類與方法 描述
    XMLEncoder .writeObject() 將一個(gè)對(duì)象序列化到外部字節(jié)流
    XMLDecoder .readObject() 從外部字節(jié)流讀取并重新構(gòu)造對(duì)象

        如果一個(gè)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>

        像AWTSwing中很多可視化組件都是bean,當(dāng)然也是可以用這種方式序列化的,下面就是從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>

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

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

    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\項(xiàng)目\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\項(xiàng)目\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


    主要類與方法 描述
    load () 從一個(gè)外部流讀取屬性
    store () 將屬性保存到外部流(特別是文件)
    getProperty () 取得一個(gè)指定的屬性
    setProperty () 設(shè)置一個(gè)指定的屬性
    list () 列出這個(gè)Properties對(duì)象包含的全部“key-value”對(duì)
    System .getProperties() 取得系統(tǒng)當(dāng)前的環(huán)境變量

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

    Properties prop = newProperties();
    prop.setProperty("key1", "value1");
    ...
    FileOutputStream out = newFileOutputStream("config.properties");
    prop.store(out, "--這里是文件頭,可以加入注釋--");

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

    主要類與方法 描述
    systemNodeForPackage () 根據(jù)指定的Class對(duì)象得到一個(gè)Preferences對(duì)象,這個(gè)對(duì)象的注冊(cè)表路徑是從“HKEY_LOCAL_MACHINE\”開始的
    systemRoot () 得到以注冊(cè)表路徑HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs 為根結(jié)點(diǎn)的Preferences對(duì)象
    userNodeForPackage () 根據(jù)指定的Class對(duì)象得到一個(gè)Preferences對(duì)象,這個(gè)對(duì)象的注冊(cè)表路徑是從“HKEY_CURRENT_USER\”開始的
    userRoot () 得到以注冊(cè)表路徑HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs 為根結(jié)點(diǎn)的Preferences對(duì)象
    putXXX() 設(shè)置一個(gè)屬性的值,這里XXX可以為基本數(shù)值型類型,如int、long等,但首字母大寫,表示參數(shù)為相應(yīng)的類型,也可以不寫而直接用put,參數(shù)則為字符串
    getXXX() 得到一個(gè)屬性的值
    exportNode () 將全部首選項(xiàng)導(dǎo)出為一個(gè)XML文件
    exportSubtree () 將部分首選項(xiàng)導(dǎo)出為一個(gè)XML文件
    importPreferences () 從XML文件導(dǎo)入首選項(xiàng)

        你可以按如下步驟保存數(shù)據(jù):

    Preferences myPrefs1 = Preferences.userNodeForPackage(this);// 這種方法是在“HKEY_CURRENT_USER\”下按當(dāng)前類的路徑建立一個(gè)注冊(cè)表項(xiàng)
    Preferences myPrefs2 = Preferences.systemNodeForPackage(this);// 這種方法是在“HKEY_LOCAL_MACHINE\”下按當(dāng)前類的路徑建立一個(gè)注冊(cè)表項(xiàng)
    Preferences myPrefs3 = Preferences.userRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_CURRENT_USER\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個(gè)注冊(cè)表項(xiàng)
    Preferences myPrefs4 = Preferences.systemRoot().node("com.jungleford.demo");// 這種方法是在“HKEY_LOCAL_MACHINE\SOFTWARE\Javasoft\Prefs\”下按“com\jungleford\demo”的路徑建立一個(gè)注冊(cè)表項(xiàng)
    myPrefs1.putInt("key1", 10);
    myPrefs1.putDouble("key2", -7.15);
    myPrefs1.put("key3", "value3");
    FileOutputStream out = newFileOutputStream("prefs.xml");
    myPrefs1.exportNode(out);

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

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

    RMI
        RMI機(jī)制其實(shí)就是RPC(遠(yuǎn)程過程調(diào)用)的Java版本,它使用socket作為基本傳輸手段,同時(shí)也是序列化最重要的一個(gè)應(yīng)用。現(xiàn)在網(wǎng)絡(luò)傳輸從編程的角度來看基本上都是以流的方式操作,socket就是一個(gè)例子,將對(duì)象轉(zhuǎn)換成字節(jié)流的一個(gè)重要目標(biāo)就是為了方便網(wǎng)絡(luò)傳輸。
        想象一下傳統(tǒng)的單機(jī)環(huán)境下的程序設(shè)計(jì),對(duì)于Java語言的函數(shù)(方法)調(diào)用(注意與C語言函數(shù)調(diào)用的區(qū)別)的參數(shù)傳遞,會(huì)有兩種情況:如果是基本數(shù)據(jù)類型,這種情況下和C語言是一樣的,采用值傳遞方式;如果是對(duì)象,則傳遞的是對(duì)象的引用,包括返回值也是引用,而不是一個(gè)完整的對(duì)象拷貝!試想一下在不同的虛擬機(jī)之 間進(jìn)行方法調(diào)用,即使是兩個(gè)完全同名同類型的對(duì)象他們也很可能是不同的引用!此外對(duì)于方法調(diào)用過程,由于被調(diào)用過程的壓棧,內(nèi)存“現(xiàn)場(chǎng)”完全被被調(diào)用者占 有,當(dāng)被調(diào)用方法返回時(shí),才將調(diào)用者的地址寫回到程序計(jì)數(shù)器(PC),恢復(fù)調(diào)用者的狀態(tài),如果是兩個(gè)虛擬機(jī),根本不可能用簡(jiǎn)單壓棧的方式來保存調(diào)用者的狀 態(tài)。因?yàn)榉N種原因,我們才需要建立RMI通信實(shí)體之間的“代理”對(duì)象,譬如“存根”就相當(dāng)于遠(yuǎn)程服務(wù)器對(duì)象在客戶機(jī)上的代理,stub就是這么來的,當(dāng)然 這是后話了。
        本地對(duì)象與遠(yuǎn)程對(duì)象(未必是物理位置上的不同機(jī)器,只要不是在同一個(gè)虛擬機(jī)內(nèi)皆為“遠(yuǎn)程”)之間傳遞參數(shù)和返回值,可能有這么幾種情形:

    • 值傳遞:這又包括兩種子情形:如果是基本數(shù)據(jù)類型,那么都是“可序列化”的,統(tǒng)統(tǒng)序列化成可傳輸?shù)淖止?jié)流;如果是對(duì)象,而且不是“遠(yuǎn)程對(duì)象”(所謂“遠(yuǎn)程對(duì)象”是實(shí)現(xiàn)了java.rmi.Remote接口的對(duì)象),本來對(duì)象傳遞的應(yīng)該是引用,但由于上述原因,引用是不足以證明對(duì)象身份的,所以傳遞的仍然是一個(gè)序列化的拷貝(當(dāng)然這個(gè)對(duì)象也必須滿足上述“可序列化”的條件)。
    • 引用傳遞:可以引用傳遞的只能是“遠(yuǎn)程對(duì)象”。這里所謂的“引用”不要理解成了真的只是一個(gè)符號(hào),它其實(shí)是一個(gè)留在(客戶機(jī))本地stub中的,和遠(yuǎn)端服務(wù)器上那個(gè)真實(shí)的對(duì)象張得一模一樣的鏡像而已!只是因?yàn)樗悬c(diǎn)“特權(quán)”(不需要經(jīng)過序列化),在本地內(nèi)存里已經(jīng)有了一個(gè)實(shí)例,真正引用的其實(shí)是這個(gè)“孿生子”。
        由此可見,序列化在RMI當(dāng)中占有多么重要的地位。

    數(shù)據(jù)庫I/O:CMP、Hibernate

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

    CMP和Hibernate
        因?yàn)槲覍?duì)J2EE的東西不是太熟悉,隨便找了點(diǎn)材料看看,所以擔(dān)心說的不到位,這次就不作具體總結(jié)了,人要學(xué)習(xí)……真是一件痛苦的事情 

    序列化再探討

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

    為什么叫做“序列”化?
        開場(chǎng)白里我說更習(xí)慣于把“Serialization”稱為“序列化”而不是“串行化”,這是有原因的。介紹這個(gè)原因之前先回顧一些計(jì)算機(jī)基本的知識(shí),我們知道現(xiàn)代計(jì)算機(jī)的內(nèi)存空間都是線性編址的 (什么是“線性”知道吧,就是一個(gè)元素只有一個(gè)唯一的“前驅(qū)”和唯一的“后繼”,當(dāng)然頭尾元素是個(gè)例外;對(duì)于地址來說,它的下一個(gè)地址當(dāng)然不可能有兩個(gè), 否則就亂套了),“地址”這個(gè)概念推廣到數(shù)據(jù)結(jié)構(gòu),就相當(dāng)于“指針”,這個(gè)在本科低年級(jí)大概就知道了。注意了,既然是線性的,那“地址”就可以看作是內(nèi)存 空間的“序號(hào)”,說明它的組織是有順序的,“序號(hào)”或者說“序列號(hào)”正是“Serialization”機(jī)制的一種體現(xiàn)。為什么這么說呢?譬如我們有兩個(gè)對(duì)象a和b,分別是類A和B的實(shí)例,它們都是可序列化的,而A和B都有一個(gè)類型為C的屬性,根據(jù)前面我們說過的原則,C當(dāng)然也必須是可序列化的。

    import java.io.*;
    ...
    class A implementsSerializable
    {
      C c;
      ...
    }

    class B implementsSerializable
    {
      C c;
      ...
    }

    class C implementsSerializable
    {
      ...
    }

    A a;
    B b;
    C c1;
    ...

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

    更靈活的“序列化”:transient屬性和Externalizable
        Serializable確實(shí)很方便,方便到你幾乎不需要做任何額外的工作就可以輕松將內(nèi)存中的對(duì)象保存到外部。但有兩個(gè)問題使得Serializable的威力收到束縛:
        一個(gè)是效率問題,《Core Java 2》中指出,Serializable使用系統(tǒng)默認(rèn)的序列化機(jī)制會(huì)影響軟件的運(yùn)行速度,因?yàn)樾枰獮槊總€(gè)屬性的引用編號(hào)和查號(hào),再加上I/O操作的時(shí)間(I/O和內(nèi)存讀寫差的可是一個(gè)數(shù)量級(jí)的大小),其代價(jià)當(dāng)然是可觀的。
        另一個(gè)困擾是“裸”的Serializable不可定制, 傻乎乎地什么都給你序列化了,不管你是不是想這么做。其實(shí)你可以有至少三種定制序列化的選擇。其中一種前面已經(jīng)提到了,就是在implements Serializable的類里面添加私有的writeObject()和readObject()方法(這種Serializable就不裸了,),在這兩個(gè)方法里,該序列化什么,不該序列化什么,那就由你說了算了,你當(dāng)然可以在這兩個(gè)方法體里面分別調(diào)用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject()仍然執(zhí)行默認(rèn)的序列化動(dòng)作(那 你在代碼上不就做無用功了?呵呵),也可以用ObjectOutputStream.writeObject()和 ObjectInputStream.readObject()方法對(duì)你中意的屬性進(jìn)行序列化。但虛擬機(jī)一看到你定義了這兩個(gè)方法,它就不再用默認(rèn)的機(jī)制 了。
        如果僅僅為了跳過某些屬性不讓它序列化,上面的動(dòng)作似乎顯得麻煩,更簡(jiǎn)單的方法是對(duì)不想序列化的屬性加上transient關(guān)鍵字,說明它是個(gè)“暫態(tài)變量”,默認(rèn)序列化的時(shí)候就不會(huì)把這些屬性也塞到外部流里了。當(dāng)然,你如果定義writeObject()和readObject()方法的化,仍然可以把暫態(tài)變量進(jìn)行序列化。題外話,像transientviolatefinallyassert這樣的關(guān)鍵字初學(xué)者可能會(huì)不太重視,而現(xiàn)在有的公司招聘就偏偏喜歡問這樣的問題
        再一個(gè)方案就是不實(shí)現(xiàn)Serializable而改成實(shí)現(xiàn)Externalizable接口。我們研究一下這兩個(gè)接口的源代碼,發(fā)現(xiàn)它們很類似,甚至容易混淆。我們要記住的是:Externalizable默認(rèn)并不保存任何對(duì)象相關(guān)信息!任何保存和恢復(fù)對(duì)象的動(dòng)作都是你自己定義的。Externalizable包含兩個(gè)public的方法:

    public void writeExternal (ObjectOutput out) throwsIOException;
    public voidreadExternal(ObjectInput in) throwsIOException, ClassNotFoundException;

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

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

    一點(diǎn)啟示

         作為一個(gè)實(shí)際的應(yīng)用,我在寫那個(gè)簡(jiǎn)易的郵件客戶端JExp的時(shí)候曾經(jīng)對(duì)比過好幾種保存Message對(duì)象(主要是幾個(gè)關(guān)鍵屬性和郵件的內(nèi)容)到本地的方 法,譬如XML、Properties等,最后還是選擇了用序列化的方式,因?yàn)檫@種方法最簡(jiǎn)單, 大約可算是“學(xué)以致用”罷。這里“存取程序狀態(tài)”其實(shí)只是一個(gè)引子話題罷了,我想說的是——就如同前面我們討論的關(guān)于logging的話題一樣——在 Java面前對(duì)同一個(gè)問題你可以有很多種solution:熟悉文件操作的,你可能會(huì)覺得Properties、XML或Bean比較方便,然后又發(fā)現(xiàn)了 還有Preferences這么一個(gè)東東,大概又會(huì)感慨“天外有天”了,等到你接觸了很多種新方法以后,結(jié)果又會(huì)“殊途同歸”,重新反省 Serialization機(jī)制本身。這不僅是Java,科學(xué)也是同樣的道理。

    參考資料

    posted on 2007-01-19 00:48 苦笑枯 閱讀(346) 評(píng)論(0)  編輯  收藏 所屬分類: Java
    收藏來自互聯(lián)網(wǎng),僅供學(xué)習(xí)。若有侵權(quán),請(qǐng)與我聯(lián)系!

    <2007年1月>
    31123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    常用鏈接

    留言簿(2)

    隨筆分類(56)

    隨筆檔案(56)

    搜索

    •  

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 国产亚洲福利精品一区| 欧美在线看片A免费观看| 国产高潮久久免费观看| 污污污视频在线免费观看| 亚洲AV无码AV男人的天堂不卡| 亚洲色无码国产精品网站可下载| 中文字幕无码亚洲欧洲日韩| 亚洲熟妇成人精品一区| 亚洲一卡2卡三卡4卡无卡下载 | 最新中文字幕免费视频| 成年轻人网站色免费看| 成人毛片免费在线观看| 日本久久久免费高清| 在线a亚洲v天堂网2018| 亚洲一级片免费看| 久久精品国产精品亚洲精品| 亚洲韩国—中文字幕| 亚洲国产美女在线观看| 激情亚洲一区国产精品| 亚洲欧洲无码AV不卡在线| 精品在线免费视频| jizz日本免费| 日韩精品无码专区免费播放| 最近最新MV在线观看免费高清| 天天摸天天操免费播放小视频| 国产免费av片在线播放| AV在线亚洲男人的天堂| 亚洲∧v久久久无码精品| 亚洲三级在线播放| 极品色天使在线婷婷天堂亚洲| 国产高清视频免费在线观看| 男人都懂www深夜免费网站| 在线永久看片免费的视频| 夫妻免费无码V看片| 亚洲欧洲精品成人久久奇米网| 亚洲产国偷V产偷V自拍色戒| 亚洲免费人成视频观看| 免费观看四虎精品成人| 免费在线看黄的网站| 女人18毛片水真多免费播放| 亚洲午夜久久久影院伊人|