前几天在一篇文章中聊到克隆的话题(参看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)