大多数的网站以及多数的java书上都会说使用StringBuffer类进行字符串”连接”操作是比String类进行连接操作的效率高的,那么真的是这样吗?在这里我们实际自己测试一下,看看他们两个到底谁的效率高,然后从反编译的代码解释原因.
在我的这篇博客:《Java中 “abc” + ‘/’和”abc” + “/”的区别》中提到了String
类的’+’操作是依赖于StringBuilder
类的,而JDK API文档中说StringBuilder
类的速度是比StringBuffer
类更快的.那么到底是String
类的效率高还是StringBuffer
类的效率高呢?这里我们自己测试一下.
程序的大体框架
import java.util.*;
public class StringTest{
private static final int COUNT = 50000;
public static void main(String[] args) {
Runtime run = Runtime.getRuntime();
long startTime, endTime;
long startMem, endMem;
// 回收一下垃圾
run.gc();
// 记录开始时的内存及时间信息,并打印到屏幕上
startTime = System.currentTimeMillis();
startMem = run.totalMemory() - run.freeMemory();
System.out.println( "time: " + (new Date()) );
System.out.println("used memory: " + startMem);
// 执行需要测试的代码段
test();
// 计算花费的内存及时间,然后打印到屏幕上
endTime = System.currentTimeMillis();
endMem = run.totalMemory() - run.freeMemory();
System.out.println("time: " + (new Date()) + ", spend time:" + (endTime-startTime));
System.out.println("used memory: " + endMem + ", added memory:" + (endMem-startMem));
}
public static String test(){
// 这里写String的连接操作或者StringBuffer的"连接"操作
}
}
-
System.currentTimeMillis();
可以得到以毫秒为单位的当前时间,所以endTime-startTime
大致就是test
方法运行的时间.因为两次获取时间当中不仅执行了test
方法还获取了系统内存,以及打印了两条信息. -
run.totalMemory() - run.freeMemory()
可以大致获取程序当前使用使用的内存,在这里我们就不解释了,有不明白的可以参考我的另一篇博客:《获取java程序运行时内存信息》.
这个大致的框架没有什么比较难以理解的地方,所以,就只解释这两条吧.下面我们把test
方法补充完整
待测试部分的程序代码
需要测试的代码非常简单: String
类测试代码
public static String test(){
String str = "";
for(int i=0; i<COUNT; ++i){
str += i;
}
return str;
}
StringBuffer
类测试代码
public static String test(){
StringBuffer strbf = new StringBuffer();
for(int i=0; i<COUNT; ++i){
strbf.append(i);
}
return strbf.toString();
}
貌似没法让这两段代码并排显示…将就着看吧
这段程序就是简单的从0连接到49999,我们创建两个类,分别用这两段代码填充就可以开始测试了.OK,一切准备就绪,那么下面就是运行程序,查看输出结果了吧?
运行结果
左边的图是测试String
类的输出结果,右边是测试StringBuffer
的结果:
|
|
额,好吧,虽然和《Java中 “abc” + ‘/’和”abc” + “/”的区别》里边的猜测完全相反,但是也不得不承认StringBuffer
类确实比String
类更快.那么,为什么呢?String
类的连接不是依赖于StringBuilder
类的吗?
从反编译的源码解释这个现象
由于反编译的代码会比较长,这里我们只给出测试String
类的test
中for
循环部分的代码.如果对测试StringBuffer
类的for
循环部分感兴趣,请自己查看.
执行下面的命令,就可以在StringTest.s中看到反编译的代码:
javap -c StringTest.class > StringTest.s
6: ldc #24 // int 50000
8: if_icmpge 36
11: new #8 // class java/lang/StringBuilder
14: dup
15: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
18: aload_0
19: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: iload_1
23: invokevirtual #25 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_0
30: iinc 1, 1
33: goto 5
如果把这段代码翻译成java代码就是下面这段代码:
for(int i=0; i<COUNT; ++i){
str = new StringBuilder().append(str).append(i).toString();
}
注:上面这段程序确实和我们最开始给出的程序一样,连反编译产生的代码都一模一样
也就是每连接一次字符串都会产生一个临时的StringBuilder
对象,同时还会舍弃以前的String
对象,所以在内存上会有很大的开销.由于拷贝字符串到新的数组,构造2n个对象以及使用内存过大导致更频繁的缺页等也导致了String
类连接字符串在时间上也比StringBuffer
类差了许多倍.所以在有较大量的字符串连接时使用StringBuilder
或者StringBuffer
好处还是挺好的.当然,在字符串连接比较少的情况下使用String
比StringBuffer
方便不少,又不会有过多的性能问题,所以完全可以使用String
类.
事实证明,String
类比StringBuffer
类快只是我一厢情愿而已.但是也得到了一些经验:有什么猜想就应该去动手验证它,即使结果可能和我们所猜想差了十万八千里,否则你永远不知道/不相信真正的答案是什么.
写于 2015/05/17