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

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

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

    licheng700

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      26 Posts :: 5 Stories :: 5 Comments :: 1 Trackbacks

    2005年9月21日 #

     

    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(即已經是“可序列化”的了),于是這些類的對象以及基本數據類型都可以直接作為你需要序列化的那個類的內部屬性。如果碰到了不是“可序列化”的屬性怎么辦?對不起,那這個屬性的類還需要事先實現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的實用類。和它相關的兩個主要類有XMLEcoderXMLDecoder,顯然,這是以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>

        像AWTSwing中很多可視化組件都是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文件等,這些文件當中的數據以“關鍵字-值”對的方式保存?!?STRONG>環境變量”這個概念都知道吧,它也是一種“key-value”對,以前也常??吹桨嫔蠁枴叭绾稳〉孟到y某某信息”之類的問題,其實很多都保存在環境變量里,只要用一條

    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\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


    主要類與方法 描述
    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\SOFTWARE\Javasoft\Prefs 為根結點的Preferences對象
    userNodeForPackage() 根據指定的Class對象得到一個Preferences對象,這個對象的注冊表路徑是從“HKEY_CURRENT_USER\”開始的
    userRoot() 得到以注冊表路徑HKEY_CURRENT_USER\SOFTWARE\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\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);

    網絡I/O:Socket→RMI

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

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

    • 值傳遞:這又包括兩種子情形:如果是基本數據類型,那么都是“可序列化”的,統統序列化成可傳輸的字節流;如果是對象,而且不是“遠程對象”(所謂“遠程對象”是實現了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()方法的化,仍然可以把暫態變量進行序列化。題外話,像transientviolate、finallyassert這樣的關鍵字初學者可能會不太重視,而現在有的公司招聘就偏偏喜歡問這樣的問題
        再一個方案就是不實現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,科學也是同樣的道理。

    posted @ 2005-09-28 10:09 小海船 閱讀(338) | 評論 (0)編輯 收藏

    1。追加文件方法
              1.java.io.FileWriter 的構造函數中:FileWriter(File file, boolean append)
              Constructs a FileWriter object given a File object.
    append - if true, then bytes will be written to the end of the file rather than the beginning

             2.類java.io.RandomAccessFile extends Object implements DataOutput, DataInput 中方法
                  seek(long pos)將當前操作指針移到文件末尾。
    2.關于隔行寫入文件java.io.BufferedWriter類中的方法newLine()



    posted @ 2005-09-27 14:00 小海船 閱讀(1189) | 評論 (1)編輯 收藏

    Log4j 學習筆記

    ccjsmile (http://ijsp.net)

     

    這是我在學習Log4j時做的一點筆記,希望對各位朋友有一點幫助。我的mail:ccjsmile@sohu.com,希望能與您進行討論^_*

     

    Log4j 是一個開放源碼項目,它是一個日志管理程序。

    Log4j的優點:

    1.       方便的調試信息;

    2.       日志以各種豐富的(主要是文件)形式保留,用于以后分析;

    缺點:減慢程序運行速度.


    (A) 其中,level 是日志記錄的優先級,分為OFFFATALERROR、WARN、INFO、DEBUG、ALL或者您定義的級別。Log4j建議只使用四個級別,優先級從高到低分別是ERROR、WARNINFO、DEBUG。通過在這里定義的級別,您可以控制到應用程序中相應級別的日志信息的開關。比如在這里定義了INFO級別,則應用程序中所有DEBUG級別的日志信息將不被打印出來。

    appenderName就是指定日志信息輸出到哪個地方。您可以同時指定多個輸出目的地。

     

    (B) 其中,Log4j提供的appender有以下幾種:

    org.apache.log4j.ConsoleAppender(控制臺),

    org.apache.log4j.FileAppender(文件),

    org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件),org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件),

    org.apache.log4j.WriterAppender(將日志信息以流格式發送到任意指定的地方)

     

    (C) 其中,Log4j提供的layout有以下幾種:

    org.apache.log4j.HTMLLayout(以HTML表格形式布局),

    org.apache.log4j.PatternLayout(可以靈活地指定布局模式),

    org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串),

    org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)

     

     

    下面介紹一下log4jweb中應用的例子:

    這是一個用于log4j初始化的servlet

     

    package net.ijsp.log4j;

     

    import org.apache.log4j.PropertyConfigurator;

    import javax.servlet.http.HttpServlet;

    import javax.servlet.ServletException;

     

    public class InitLog4j extends HttpServlet {

     

      public  void init() throws ServletException  {

       PropertyConfigurator.configure("D:/resin/webapps/log4j/web-inf/classes/log4j.properties");

        System.out.println("ok");

      }

    }

     

    在上述文件中我們發現需要一個log4j.properties的文件,他的存放路徑為:D:/resin/webapps/log4j/web-inf/classes/log4j.properties

    這個properties的文件內容如下:

    #log4j.properties

    #Set root logger level to DEBUG and its only appender to A1.

    log4j.rootLogger=INFO,A1

    #A1 is set to be a ConsoleAppender.

    #log4j.appender.A1=org.apache.log4j.ConsoleAppender

           log4j.appender.A1=org.apache.log4j.RollingFileAppender

           log4j.appender.A1.File=example11.log

    #A1 uses PatternLayout

           log4j.appender.A1.layout=org.apache.log4j.PatternLayout

           log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %c \n- %m%n\n"

     

    log4j.logger.ltestlog4j=INFO,A2

    log4j.appender.A2=org.apache.log4j.ConsoleAppender

           log4j.appender.A2.layout=org.apache.log4j.PatternLayout

           log4j.appender.A2.layout.ConversionPattern=%d [%t] %-5p %c \n- %m%n\n"

     

    #log4j.appender.A1.MaxFileSize=1000KB

    # Keep one backup file

    #log4j.appender.A1.MaxBackupIndex=1

     

    因為這是一個servlet文件,同時我們還要修改web.xml文件

    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

    <web-app>

           <servlet>

                  <servlet-name>log4jinit</servlet-name>

                  <servlet-class>net.ijsp.log4j.InitLog4j</servlet-class>

                  <load-on-startup>1</load-on-startup>

      </servlet>

    </web-app>

     

    下面這兩個為測試文件:

    package net.ijsp.log4j;

     

    import org.apache.log4j.PropertyConfigurator;

    import org.apache.log4j.Logger;

    import javax.servlet.http.HttpServlet;

    import javax.servlet.ServletException;

     

    public class Test {

     

      public Test() {}

     

      static Logger logger =Logger.getRootLogger();

      static Logger logger1 = Logger.getLogger("ltestlog4j");

     

      public void t() {

        logger.error("sssssssssss");

        System.out.println(logger);

        logger1.error("kjdlfkj");

        System.out.println("ddddddddddddddd");

      }

    }

     

     

    <%@page import ="net.ijsp.log4j.*"%>

     

    <%

    Test t = new Test();

    t.t();

    %>

    posted @ 2005-09-26 17:27 小海船 閱讀(338) | 評論 (0)編輯 收藏


    Last login: Fri Sep 23 14:37:23 2005 from 211.155.247.222
    Sun Microsystems Inc.   SunOS 5.9       Generic May 2002
    [iplanet@ec2 sunone]$ telnet 10.211.12.22
    Trying 10.211.12.22...
    Connected to 10.211.12.22.
    Escape character is '^]'.


    SunOS 5.9

    login: oracle
    Password:
    Last login: Mon Sep 26 06:39:46 from 10.211.12.233
    Sun Microsystems Inc.   SunOS 5.9       Generic May 2002
    You have mail.
    [oracle@db oracle]$ sqlplus cpf/cpf

    SQL*Plus: Release 9.2.0.5.0 - Production on Mon Sep 26 09:24:44 2005

    Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


    Connected to:
    Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
    With the Partitioning, OLAP and Oracle Data Mining options
    JServer Release 9.2.0.5.0 - Production

    SQL> drop table bs_banksetting
      2  ;

    Table dropped.

    SQL> drop table bs_bankaccountinfo;

    Table dropped.

    SQL> drop table bs_clientsetting;

    Table dropped.

    SQL> drop table bs_countrysetting;

    Table dropped.

    SQL> drop table bs_currencysetting;

    Table dropped.

    SQL> exit
    Disconnected from Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
    With the Partitioning, OLAP and Oracle Data Mining options
    JServer Release 9.2.0.5.0 - Production
    [oracle@db oracle]$ ftp 211.155.247.197
    Connected to 211.155.247.197.
    220 fsdev FTP server ready.
    Name (211.155.247.197:oracle): root
    331 Password required for root.
    Password:
    230 User root logged in.
    Remote system type is UNIX.
    Using binary mode to transfer files.
    ftp> cd bankportal
    250 CWD command successful.
    ftp> ls
    200 PORT command successful.
    150 Opening ASCII mode data connection for file list.
    AcctDataSourceType.class
    AcctDataSourceType.java
    CurrencyMappingDAO_oracle.class
    SettingData.dmp
    account
    addp.jsp
    bankportal.properties
    create_table.sql
    datamaintain
    example.xls
    expbp0926.dmp
    expbp1420.dmp
    iTreasury-bankportal.ear
    itreasury.properties
    menubp.dmp
    model.xls
    query
    v003.jsp
    v005.jsp
    226 Transfer complete.
    316 bytes received in 0.016 seconds (19.33 Kbytes/s)
    ftp> get expbp0926.dmp
    200 PORT command successful.
    150 Opening BINARY mode data connection for expbp0926.dmp (47104 bytes).
    226 Transfer complete.
    local: expbp0926.dmp remote: expbp0926.dmp
    47104 bytes received in 0.16 seconds (292.70 Kbytes/s)
    ftp> bye
    221-You have transferred 47104 bytes in 1 files.
    221-Total traffic for this session was 48051 bytes in 2 transfers.
    221-Thank you for using the FTP service on fsdev.
    221 Goodbye.
    [oracle@db oracle]$ imp system/manager1 file=expbp0926.dmp fromuser=bp_cpf touser=cpf

    Import: Release 9.2.0.5.0 - Production on Mon Sep 26 09:30:09 2005

    Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.


    Connected to: Oracle9i Enterprise Edition Release 9.2.0.5.0 - 64bit Production
    With the Partitioning, OLAP and Oracle Data Mining options
    JServer Release 9.2.0.5.0 - Production

    Export file created by EXPORT:V09.02.00 via conventional path

    Warning: the objects were exported by BP_CPF, not by you

    import done in ZHS16GBK character set and AL16UTF16 NCHAR character set
    . importing BP_CPF's objects into CPF
    . . importing table           "BS_BANKACCOUNTINFO"        150 rows imported
    . . importing table               "BS_BANKSETTING"        107 rows imported
    . . importing table             "BS_CLIENTSETTING"         27 rows imported
    . . importing table            "BS_COUNTRYSETTING"         21 rows imported
    . . importing table           "BS_CURRENCYSETTING"         53 rows imported
    Import terminated successfully without warnings.
    [oracle@db oracle]$

    posted @ 2005-09-26 09:38 小海船 閱讀(281) | 評論 (0)編輯 收藏

    package property;

    import java.io.*;
    import java.util.*;

    public class TemplateId {

     private static Properties p;

     private static final TemplateId pi = new TemplateId();

     public TemplateId() {
      // 從templateId.properties屬性文件獲得數據
      InputStream is = getClass()
        .getResourceAsStream("templateId.properties");
      p = new Properties();
      try {
       p.load(is);
      } catch (IOException ex) {
       ex.printStackTrace();
      }
     }

     // 此處的templateId就是templateId.properties屬性文件中的templateId。
     public static String getTemplateId() {
      return pi.p.getProperty("templateId");
     }

     public static void main(String args[]) {
      System.out.println("templateId=" + getTemplateId()); // 測試調用
     }
    }
    templateId.properties文件內容:templateId=FFD4156506-3-2F8CAC7

    posted @ 2005-09-21 17:51 小海船 閱讀(360) | 評論 (0)編輯 收藏

            所謂熱部署,就是在應用正在運行的時候升級軟件,卻不需要重新啟動應用。對于Java應用程序來說,熱部署就是在運行時更新Java類文件。在基于Java的應用服務器實現熱部署的過程中,類裝入器扮演著重要的角色。大多數基于Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。類裝入器不能重新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就可以將類再次裝入一個正在運行的應用程序。

           由于類裝入器擔負著把代碼裝入JVM的重任,所以它的體系結構應當保證整個平臺的安全性不會受到威脅。每一個類裝入器要為它裝入的類定義一個獨立的名稱空間,因此運行時,一個類由它的包名稱和裝入它的類裝入器兩者結合唯一地標識。

      在名稱空間之外,類不可見,運行時不同名稱空間的類之間有一種保護性的“隔離墻”,在Java 2向父類委托的類裝入模式中,類裝入器可以請求其父類裝入器裝入的類,因此類裝入器需要的類不一定全部由它自己裝入。

      在Java運行環境中,不同的類裝入器從不同的代碼庫裝入類,之所以要將各個類裝入器代碼庫的位置分開,是為了便于給不同的代碼庫設定不同的信任級別。在JVM中,由bootstrap類裝入器裝入的類具有最高的信任級別,用戶自定義類裝入器代碼庫的信任級別最低。此外,類裝入器可以把每一個裝入的類放入一個保護域,保護域定義了代碼執行時所擁有的權限。

      如果要以系統安全策略(一個java.security.Policy的實例)為基礎定義代碼的權限,定制類裝入器必須擴展java.security.SecureClassLoad類,調用其defineClass方法,SecureClassLoad類的defineClass方法有一個java.security.CodeSource參數。defindClass方法從系統策略獲得與CodeSource關聯的權限,以此為基礎定義一個java.security.ProtectionDomain。詳細介紹該安全模型已經超出了本文的范圍,請讀者自行參閱有關JVM內部機制的資料。

     類裝入器裝入的最小執行單元是Java .class文件。Java .class文件包含Java類的二進制描述,其中有可執行的字節碼以及該類用到的對其他類的引用,包括對Java標準API里面的類的引用。簡單地說,類裝入器首先找到要裝入的Java類的字節碼,讀入字節碼,創建一個java.lang.Class類的實例。做好這些準備之后,類就可以被JVM執行了。
     當JVM最初開始運行時,它里面不裝入任何類。如果要求JVM執行一個程序,被執行的類首先裝入,字節碼執行期間會引用到其他類和接口,這些被引用到的類和接口隨之也被裝入。因此,有人把JVM的類裝入方式稱為“懶惰的”裝入方式,即只有必須用到某個類時才會裝入它(而不是預先裝入各種可能用到的類),正因為如此,開始時JVM不必知道運行時要裝入哪些類。在Java平臺上,懶惰的裝入方式是實現動態可擴展性機制的關鍵因素之一。在本文的后面,你將會看到通過實現一個定制的Java類裝入器,我們可以為Java運行時環境加入許多有趣的功能
       一、委托模式
         Java 2運行時環境中有多個類裝入器的實例,每一個類裝入器的實例從不同的代碼庫裝入Java類。例如,Java核心API類由bootstrap(或primordial)類裝入器裝入,應用程序的類由system(或application)類裝入器裝入。另外,應用程序可以自定義類裝入器從指定的代碼庫裝入類。Java 2定義了類裝入器之間的父-子關系,每一個類裝入器(bootstrap除外)都有一個父類裝入器,形成一個由類裝入器構成的樹形結構,bootstrap類裝入器是這個樹形結構的根,因此bootstrap沒有父類裝入器。
     當客戶程序請求類裝入器裝入某個類時,類裝入器按照下面的算法裝入一個類:
       首先執行一次檢查,查看客戶程序請求的類是否已經由當前的類裝入器裝入。如果是,則返回已裝入的類,請求處理完畢。JVM緩沖了類裝入器裝入的所有類,已經裝入的類不會被再次裝入。

     如果尚未裝入類,則裝入類的請求被委托給父類裝入器,這個操作發生在當前的類裝入器嘗試裝入指定的類之前。委托裝入類的操作一直向上傳遞,直至bootstrap類裝入器,如前所述,bootstrap類裝入器是處于樹形結構的頂層,它不再有可委托的父類裝入器。

     如果父類裝入器未能裝入指定的類,則當前的類裝入器將嘗試搜索該類。每一個類裝入器有預定義的搜索和裝入類的位置。例如,bootstrap類裝入器搜索sun.boot.class.path系統屬性中指定的位置(包括目錄和zip、jar文件),system類裝入器搜索的位置由JVM開始運行時傳入的CLASSPATH命令行變量指定(相當于設置java.class.path系統屬性)。如果找到了類,則類裝入器將類返回給系統,請求處理完畢。
       
    ?、?/STRONG> 如果找不到類,則拋出java.lang.ClassNotFoundException異常。
    posted @ 2005-09-21 15:50 小海船 閱讀(1480) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 亚洲国产成人久久综合一区| 无码天堂亚洲国产AV| 成人免费视频观看无遮挡| 美女18毛片免费视频| 亚洲AV电影院在线观看| 午夜一级免费视频| a毛看片免费观看视频| 亚洲成人激情小说| 亚洲级αV无码毛片久久精品| 成年人在线免费看视频| 最新亚洲成av人免费看| 亚洲成a∧人片在线观看无码| 亚洲成av人片天堂网| 最好免费观看韩国+日本| 全免费a级毛片免费看| 国产成人久久精品亚洲小说| 色婷婷亚洲十月十月色天| 亚洲国产成人久久一区WWW| 亚洲三级高清免费| 免费h视频在线观看| 黄色一级毛片免费看| 亚洲成A人片在线播放器| 亚洲一区免费观看| 亚洲香蕉网久久综合影视| 免费精品一区二区三区在线观看| 亚洲午夜免费视频| 精品国产免费人成网站| 校园亚洲春色另类小说合集| 亚洲大尺码专区影院| 亚洲VA中文字幕不卡无码| 亚洲精品人成无码中文毛片| 蜜桃视频在线观看免费网址入口| 男人进去女人爽免费视频国产| 特级毛片全部免费播放| 亚洲欧美日韩中文二区| 亚洲成人福利在线观看| 亚洲日本一区二区三区| 国产亚洲AV无码AV男人的天堂| 免费少妇a级毛片| 日本19禁啪啪无遮挡免费动图| 成人性生交大片免费看午夜a|