循證克隆

    前幾天在一篇文章中聊到克隆的話題(參看http://blog.csdn.net/rosen/archive/2004/10/09/129948.aspx)。有朋友對我所提出的克隆可以提高效率深表懷疑,今天我就來具體說明一下。

現在有一典型的 VO 類 Auto(LightWeight):

package com.test;

public class Auto implements Cloneable{
 
    private String No;
    private String Addr;
 
    public Object clone() throws CloneNotSupportedException{
 return super.clone();
    }

    public String setNo(String no){
        return this.No=no;
    } 
    public String getNo(){
        return this.No;
    } 
   
    public String setAddr(String addr){
        return this.Addr=addr;
    } 
    public String getAddr(){
        return this.Addr;
    }
}


    接着分別通過使用克隆和不使用克隆的 Bean 來構造 Auto 實例。

使用克隆的 Bean:

package com.test;

import java.io.*;
import java.util.*;
import org.dom4j.*;
import org.dom4j.io.*;

public class MyXMLReader {

    Auto auto=new Auto();                                 //請比較不同
    ArrayList al=new ArrayList();

    public ArrayList go() {
 long lasting = System.currentTimeMillis();
 try {
     File f = new File("data_100k.xml");
     SAXReader reader = new SAXReader();
     Document doc = reader.read(f);
     Element root = doc.getRootElement();
     Element foo;
     for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {
         foo = (Element) i.next();
         auto.setNo(foo.elementText("NO"));
         auto.setAddr(foo.elementText("ADDR"));
                al.add(auto.clone());                    //請比較不同
     }
 } catch (Exception e) {
     e.printStackTrace();
 }
 System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒");
 return al;
    }
}


    取八次運行時間
    運行時間:172 毫秒
    運行時間:93 毫秒
    運行時間:94 毫秒
    運行時間:141 毫秒
    運行時間:125 毫秒
    運行時間:78 毫秒
    運行時間:203 毫秒
    運行時間:63 毫秒

沒有使用克隆的 Bean:

package com.test;

import java.io.*;
import java.util.*;
import org.dom4j.*;
import org.dom4j.io.*;

public class MyXMLReader {

    ArrayList al=new ArrayList();

    public ArrayList go() {
        long lasting = System.currentTimeMillis();
 try {
     File f = new File("data_100k.xml");
     SAXReader reader = new SAXReader();
     Document doc = reader.read(f);
     Element root = doc.getRootElement();
     Element foo;
     for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {
  foo = (Element) i.next();
  Auto auto=new Auto();                     //請比較不同
         auto.setNo(foo.elementText("NO"));
         auto.setAddr(foo.elementText("ADDR"));
                al.add(auto);                             //請比較不同
     }
 } catch (Exception e) {
     e.printStackTrace();
 }
 System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒");
 return al;
    }
}


    取八次運行時間
    運行時間:187 毫秒
    運行時間:93 毫秒
    運行時間:172 毫秒
    運行時間:78 毫秒
    運行時間:204 毫秒
    運行時間:79 毫秒
    運行時間:204 毫秒
    運行時間:78 毫秒

    通過比較,克隆與非克隆,在構造 Auto 上花的時間是差不多的。
    且慢,讓我們再看下面的 Auto 類。

修改一下 Auto 類的構造函數,像這樣(HeavyWeight):

package com.test;
import java.io.*;

public class Auto implements Cloneable{
 
    private String No;
    private String Addr;
    private String str;
    private File f = new File("data_10k.xml");
    private StringBuffer sb=new StringBuffer();
 
    public Object clone() throws CloneNotSupportedException{
 return super.clone();
    }

    //以下方法僅僅爲了構造一個 HeavyWeight 對象。
    public Auto(){
        try{
            BufferedReader inbuffer=new BufferedReader(new FileReader(f));
     while ((str=inbuffer.readLine())!=null){
  sb.append(str);
     }
 }catch(Exception e){
     System.out.println(e.toString());
 }
    }

    public String setNo(String no){
        return this.No=no;
    } 
    public String getNo(){
        return this.No;
    } 
   
    public String setAddr(String addr){
        return this.Addr=addr;
    } 
    public String getAddr(){
        return this.Addr;
    }
}


    再看看測試數據呢?

    使用克隆構造 Auto 實例:     不使用克隆構造 Auto 實例:

    運行時間:188 毫秒           運行時間:2219 毫秒
    運行時間:78 毫秒            運行時間:2266 毫秒
    運行時間:109 毫秒           運行時間:2156 毫秒
    運行時間:219 毫秒           運行時間:2093 毫秒
    運行時間:110 毫秒           運行時間:2266 毫秒
    運行時間:78 毫秒            運行時間:2141 毫秒
    運行時間:157 毫秒           運行時間:2078 毫秒
    運行時間:78 毫秒            運行時間:2172 毫秒

    好了,讓我們查看一下 Auto 類。可以看見只是在其構造函數中加入讀取10K XML文件的代碼,而克隆與非克隆運行時間卻有近 10 倍的差距!
   
   
    爲什麼會這樣?

    對象的構建不僅僅是“分配內存+初始化一些值域”那麼簡單,它可能涉及非常多個步驟。所以將“待建對象”的數量和體積減到最低,纔是明智之舉。

    讓我們看看創建一個 LightWeight 類都發生了什麼:

    1、從 heap 分配內存,用來存放 Auto 類的實例變量,以及一份“實現專署數據”。
    2、Auto 類的實例變量 No 和 Addr,被初始化爲缺省值,缺省值都爲null。
    3、調用 Auto 類構造函數。
    4、Auto 類構造函數調用其超類(java.lang.Object)的構造函數。
    5、java.lang.Object 構造函數返回。
    6、對象引用“auto”指向 heap 中完成的 Auto 對象。
   
    再讓我們看看創建一個 HeavyWeight 類都發生了什麼:

    1、從 heap 分配內存,用來存放 Auto 類的實例變量,以及一份“實現專署數據”。
    2、Auto 類的實例變量 No、Addr、str、f、sb,被初始化爲缺省值,缺省值都爲null。
    3、File 類的構造函數載入 10K XML 文件得到實例變量 f,調用 StringBuffer 的構造函數得到實例變量 sb,接着調用 Auto 類構造函數。(在構造函數本體執行之前,所有實例變量的初始設定值和初始化區段先獲得執行,然後才執行構造函數本體。針對 f 和 sb,又從步驟 1 開始重複這個過程。)
    4、Auto 類構造函數中調用 FileReader 類的構造函數將實例變量 f 載入,接着調用 BufferedReader 類的構造函數將 FileReader 類的實例載入,得到局部變量 inbuffer。(針對 FileReader 類的實例和 inbuffer,又從步驟 1 開始重複這個過程。)
    5、讀取 inbuffer 中的數據,實例變量 sb 被循環賦值。
    6、跳出循環後,實例變量 sb 經過方法調用返回給實例變量 str。
    7、Auto 類構造函數調用其超類(java.lang.Object)的構造函數。
    8、java.lang.Object 構造函數返回。
    9、對象引用“auto”指向 heap 中完成的 Auto 對象。
   
    通過比較可以看出,建立 HeavyWeight 對象比建立 LightWeight 對象的性能相差很多。步驟 3、4 代價最高,因爲它不得不對四個聚合對象重複全過程。
    創建對象代價高昂(特別是 HeavyWeight 對象)!應儘量減少創建次數。創建對象的次數越少,意味代碼執行越快。對於 Auto 類(HeavyWeight),複用對象引用“auto”所指向的對象纔是正解。
    對象複用的一種重要方式就是克隆。任何類如果支持克隆操作,就必須實現 Cloneable 接口,這只是一個標識接口,它並沒有實現任何方法。任何類如果實現 Cloneable,就宣佈它支持克隆操作。正如以上這些代碼,利用克隆來提高性能是非常簡單的。


(請注意!引用、轉貼本文應註明原作者:Rosen Jiang 以及出處:http://blog.csdn.net/rosen

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章