String类代表字符串,java程序中所有的字符串字面值(如”abc”)都作为此类来实现,字符串是常量,它们的值在创建后就不能改变。因为字符串不可变,因此可以共享它们。
StringBuffer,线程安全的可变字符序列。一个类似于String的字符缓冲区,但不能修改,是指这个对象本身不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
StringBuilder, 一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。
有时候,我们经常会这样写程序,譬如
public class Concatenation {
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
}
然后我们运行程序,控制台便马上打印出了我们想要的结果。可是,在这个程序片段的内部,它的执行机理以及这样写,性能上是个什么样的情况,我们可能很少去研究,下面我将来谈一谈重载的’+’操作符与StringBuilder的区别。
首先,我们先说String。众所周知,String是java.lang包中的一个类,它在java编程中使用的非常频繁。查看javaAPI可以看到,这个类的声明是public final class String….,正因为是final的,可以想到这个类已经创建,那么它就是不可变的。有了这个认识,我们再说上面的程序,每用’+’操作符对String对象进行一次运算,那么它都会产生一个新的String对象,这个新产生的String对象既包含原来的String对象也包含添加进来的String对象。毫无疑问,上面那段代码必定能很好的工作,只是为了把几个字符串拼接到一起,按照上面的做法会产生大量的”中间”String对象,这些中间对象也是需要被GC回收的。为了看清楚这段代码的执行细节,我们可以用这么一个命令
Javap –c Concatenation
控制台给我们列出了这样的信息:
Compiled from "Concatenation.java"
public class chapter12.Concatenation extends java.lang.Object{
public chapter12.Concatenation();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16; //String mango
2: astore_1
3: new #18; //class java/lang/StringBuilder
6: dup
7: ldc #20; //String abc
9: invokespecial #22; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
12: aload_1
13:invokevirtual #25;//Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: ldc #29; //String def
18: invokevirtual #25; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
21: bipush 47
23: invokevirtual #31; //Method java/lang/StringBuilder.append:(I)Ljava/la
ng/StringBuilder;
26: invokevirtual #34; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
29: astore_2
30: getstatic #38; //Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_2
34: invokevirtual #44; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
37: return
这个是经JVM编译后产生的字节码信息,这里我们只需要关心//后面的注释就可以了。
由上面展示的我们可以看到,在上面程序执行的内部,其实是有JVM帮我们产生了已StringBuilder的对象用来创建一个String,然后通过四次调用append方法来来把那些小段字符串拼接到一起。JVM之所以会自行生成一个StringBuilder对象,是因为它是更有效率的。
再来看下面的例子:
public class WhitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for (int i = 0; i < fields.length; i++)
result += fields[i];
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < fields.length; i++)
result.append(fields[i]);
return result.toString();
}
}
在第一个方法implicit中使用的是String,用+ += 操作符拼接字符;第二个方法explicit中使用的是StringBuilder,用append方法追加字符,下面我们依旧用javap命令来查看这两个方法的内部执行机理.
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #16; //String
2: astore_2
3: iconst_0
4: istore_3
5: goto 32
8: new #18; //class java/lang/StringBuilder
11: dup
12: aload_2
13: invokestatic #20; //Method java/lang/String.valueOf:(Ljava/lang/Objec
t;)Ljava/lang/String;
16: invokespecial #26; //Method java/lang/StringBuilder."<init>":(Ljava/la
ng/String;)V
19: aload_1
20: iload_3
21: aaload
22: invokevirtual #29; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
25: invokevirtual #33; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
28: astore_2
29: iinc 3, 1
32: iload_3
33: aload_1
34: arraylength
35: if_icmplt 8
38: aload_2
39: areturn
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #18; //class java/lang/StringBuilder
3: dup
4: invokespecial #45; //Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: goto 24
13: aload_2
14: aload_1
15: iload_3
16: aaload
17: invokevirtual #29; //Method java/lang/StringBuilder.append:(Ljava/lang
/String;)Ljava/lang/StringBuilder;
20: pop
21: iinc 3, 1
24: iload_3
25: aload_1
26: arraylength
27: if_icmplt 13
30: aload_2
31: invokevirtual #33; //Method java/lang/StringBuilder.toString:()Ljava/l
ang/String;
34: areturn
从两分字节码可以看出,第二个的更短小简洁,并且第一个在每个循环中都会创建一个StringBuilder对象,而第二个方法始终只有一个StringBuilder对象。然而这两者的性能差异确实有很大差别的。
所以,当我们的连接操作很简单的时候,可以使用String,但是若涉及到循环,那么最好使用StrignBuilder.