大多数的网站以及多数的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
str += i;
}
return str;
}

StringBuffer类测试代码

public static String test(){
StringBuffer strbf = new StringBuffer();
for(int i=0; i
strbf.append(i);
}
return strbf.toString();
}

貌似没法让这两段代码并排显示…将就着看吧

这段程序就是简单的从0连接到49999,我们创建两个类,分别用这两段代码填充就可以开始测试了.OK,一切准备就绪,那么下面就是运行程序,查看输出结果了吧?

运行结果

左边的图是测试String类的输出结果,右边是测试StringBuffer的结果:


Java中string的contain是 java string contains效率_System

String类的测试结果

Java中string的contain是 java string contains效率_System

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."":()V18: 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
str = new StringBuilder().append(str).append(i).toString();
}

注:上面这段程序确实和我们最开始给出的程序一样,连反编译产生的代码都一模一样

也就是每连接一次字符串都会产生一个临时的StringBuilder对象,同时还会舍弃以前的String对象,所以在内存上会有很大的开销.由于拷贝字符串到新的数组,构造2n个对象以及使用内存过大导致更频繁的缺页等也导致了String类连接字符串在时间上也比StringBuffer类差了许多倍.所以在有较大量的字符串连接时使用StringBuilder或者StringBuffer好处还是挺好的.当然,在字符串连接比较少的情况下使用String比StringBuffer方便不少,又不会有过多的性能问题,所以完全可以使用String类.

事实证明,String类比StringBuffer类快只是我一厢情愿而已.但是也得到了一些经验:有什么猜想就应该去动手验证它,即使结果可能和我们所猜想差了十万八千里,否则你永远不知道/不相信真正的答案是什么.