为了直观比较三者的性能,先做测试如下:
package string;
import org.junit.Test;
public class Effective {
private final int LOOP_TIMES = 50000;
private final String CONSTANT_STRING = "min-snail";
public void stringTest() {
long startTime = System.currentTimeMillis();
String test = "";
int loop = LOOP_TIMES;
while (loop-- > 0) {
test += CONSTANT_STRING;
}
long endTime = System.currentTimeMillis();
System.out.println("String cost: " + (endTime - startTime));
}
public void stringBuilderTest() {
long startTime = System.currentTimeMillis();
StringBuilder test = new StringBuilder();
int loop = LOOP_TIMES;
while (loop-- > 0) {
test.append(CONSTANT_STRING);
}
test.toString();
long endTime = System.currentTimeMillis();
System.out.println("StringBuilder cost: " + (endTime - startTime));
}
public void stringBufferTest() {
long startTime = System.currentTimeMillis();
StringBuffer test = new StringBuffer();
int loop = LOOP_TIMES;
while (loop-- > 0) {
test.append(CONSTANT_STRING);
}
test.toString();
long endTime = System.currentTimeMillis();
System.out.println("StringBuffer cost: " + (endTime - startTime));
}
@Test
public void test() {
for(int i=0;i<5;i++){
System.out.println(i+"----------------------------------");
stringBufferTest();
stringBuilderTest();
}
}
}
测试结果如下:
可以看出,在循环体内多次做字符串加操作,平均速度StringBuilder > StringBuffer >> String “+=”,下面从代码进行分析,StringBuilder,StringBuffer均继承于AbstractStringBuilder,不同的是,StringBuffer中append方法加了synchronized,而StringBuilder没有。
StringBuilder 是 java 为 StringBuffer 提供的一个等价类, 但不保证同步。在不涉及多线程的操作情况下可以简易的替换 StringBuffer 来提升系统性能; StringBuffer 在性能上稍略于 StringBuilder, 但可以不用考虑线程安全问题;
String 的 "+" 符号操作起来简单方便,String 的使用也很简单便捷, java 底层会转换成 StringBuilder 来实现。
测试:
public static void main(String[] args) {
String a = "hello";
String b = " world";
b = a + b;
System.out.println(b);
}
使用jad对上述代码进行,jad的使用:
(1)保证你要反编译的class 文件和jad.exe放在一个目录下
(2)开始 --> 运行 --> cmd , 找到jad 所在的目录
(3)jad -o -a -s .java Effective.class
反编译后生成的代码如下:
public static void main(String args[])
{
String s = "hello";
// 0 0:ldc1 #22 <String "hello">
// 1 2:astore_1
String s1 = " world";
// 2 3:ldc1 #23 <String " world">
// 3 5:astore_2
s1 = (new StringBuilder()).append(s).append(s1).toString();
// 4 6:new #8 <Class StringBuilder>
// 5 9:dup
// 6 10:invokespecial #9 <Method void StringBuilder()>
// 7 13:aload_1
// 8 14:invokevirtual #10 <Method StringBuilder StringBuilder.append(String)>
// 9 17:aload_2
// 10 18:invokevirtual #10 <Method StringBuilder StringBuilder.append(String)>
// 11 21:invokevirtual #11 <Method String StringBuilder.toString()>
// 12 24:astore_2
System.out.println(s1);
// 13 25:getstatic #12 <Field PrintStream System.out>
// 14 28:aload_2
// 15 29:invokevirtual #15 <Method void PrintStream.println(String)>
// 16 32:return
}
可以看出,"+"的底层实现是通过StringBuilder实例的append()方法来完成的
在本文开头测试中,使用jconsole查看堆内存的使用情况如下:
String "+" 每一次都先去创建 StringBuilder 对象, 然后再调 append() 方法来完成。这里的大部分时间都花在了对象的创建上, 而且每个创建出来的对象的生命都不能长久, 朝生夕灭, 因为这些对象创建出来之后没有引用变量来引用它们,那么它们在使用完成时候就处于一种不可到达状态, GC就会不定期的来回收这些垃圾对象。因此会看到上图堆内存中的曲线起伏变化很大。
值得注意的是,对于String str="hello"+" world!"的执行却是很快的,因为str在编译期就可以被确定是一个字符常量。当编译完成之后 str的值就是 "hello world!"。