首先,String 是用来表示一个字符串常量的,它是一个不可变对象,意味着,一旦咱们建立了某个字符串以后,就不能再改变它的值了,咱们能够从它的源码中看到,它是使用一个 final 的数组来存放内容的,即表示它是一个常量。html
/** The value is used for character storage. */ private final char value[];
接下来看个例子:java
public class Main { public static void main(String[] args) { String s1 = "a"; String s2 = "a"; String s3 = new String("a"); System.out.println(s1 == s2); // true System.out.println(s1 == s3); // false String s4 = new String("b"); String s5 = new String("b"); System.out.println(s4 == s5); // false String s6 = "a" + "b"; String s7 = "ab"; System.out.println(s6 == s7); // true String s8 = new String("ab"); System.out.println(s6 == s8); // false System.out.println(s7 == s8); //false } }
由于 String 是存放在常量池中的,虚拟机会对其进行优化,上面,虽然声明了 8 个变量,但是在常量池中只存放了 "a","b","ab" 这三个常量,使用 jclasslib 查看可知,以下所示:数组
声明一个字符串对象,若是能在常量池中找到,则直接把引用指向它便可,找不到才会建立,以后放到常量池中。安全
因为,字符串是不可变对象,若是多个字符串相加,则会出现什么呢?多线程
String s1 = "a" + "b" + "c" + "d" + "e"; System.out.println(s1);
分析字节码:oracle
能够看到,上述字符串相加后,JVM仍是一次性把 “abcde”加载到内存中,此时常量池中只有一个字符串变量,即 "abcde",以下所示:app
ldc指令:把字符串加载到常量池中(Push item from run-time constant pool),更多JVM指令参考:JVM指令jvm
也就是说 经过 "a" + "b" + "c" + "d" + "e" 和 "abcde" 对于 JVM 来讲是一回事,只会加载一次;性能
可是经过下面这种方式相加会有什么变化呢?优化
String s1 = "a"; String s2 = "b"; String s3 = "c"; String s4 = "d"; String s5 = "e"; String s6 = s1 + s2 + s3 + s4 + s5; System.out.println(s6);
首先,看下,JVM调用几回 ldc 指令加载字符串到常量池中:
再来看看这种方式一个所有的字节码:
0 ldc #2 <a> 2 astore_1 3 ldc #3 <b> 5 astore_2 6 ldc #4 <c> 8 astore_3 9 ldc #5 <d> 11 astore 4 13 ldc #6 <e> 15 astore 5 17 new #7 <java/lang/StringBuilder> 20 dup 21 invokespecial #8 <java/lang/StringBuilder.<init>> 24 aload_1 25 invokevirtual #9 <java/lang/StringBuilder.append> 28 aload_2 29 invokevirtual #9 <java/lang/StringBuilder.append> 32 aload_3 33 invokevirtual #9 <java/lang/StringBuilder.append> 36 aload 4 38 invokevirtual #9 <java/lang/StringBuilder.append> 41 aload 5 43 invokevirtual #9 <java/lang/StringBuilder.append> 46 invokevirtual #10 <java/lang/StringBuilder.toString> 49 astore 6
前面部分,调用 ldc 指令加载字符串到常量池中,后面部分能够看到,JVM 会对这种字符串相加的方式进行优化,使用 StringBuilder 来进行字符串的拼接,
StringBuffer 它是一个可变字符串,从源码中能够看到,它用来存放元素的 char 数组没有使用 final 修饰,且,char 数组的初始大小为 16.:
StringBuffer sb = new StringBuffer(); | | public StringBuffer() { super(16); } | | AbstractStringBuilder(int capacity) { value = new char[capacity]; } | | char[] value;
以后使用它的 append 方法进行字符串的链接:
String s1 = "a"; String s2 = "b"; String s3 = "c"; String s4 = "d"; String s5 = "e"; StringBuffer sb = new StringBuffer(); sb.append(s1).append(s2).append(s3).append(s4).append(s5);
字节码以下:
0 ldc #2 <a> 2 astore_1 3 ldc #3 <b> 5 astore_2 6 ldc #4 <c> 8 astore_3 9 ldc #5 <d> 11 astore 4 13 ldc #6 <e> 15 astore 5 17 new #7 <java/lang/StringBuffer> 20 dup 21 invokespecial #8 <java/lang/StringBuffer.<init>> 24 astore 6 26 aload 6 28 aload_1 29 invokevirtual #9 <java/lang/StringBuffer.append> 32 aload_2 33 invokevirtual #9 <java/lang/StringBuffer.append> 36 aload_3 37 invokevirtual #9 <java/lang/StringBuffer.append> 40 aload 4 42 invokevirtual #9 <java/lang/StringBuffer.append> 45 aload 5 47 invokevirtual #9 <java/lang/StringBuffer.append> 50 pop 51 return
此外,StringBuffer 仍是线程安全的,可在多线程下使用,append 方法被 synchronized 修饰,以保证同步:
public synchronized StringBuffer append(Object obj) { toStringCache = null; super.append(String.valueOf(obj)); return this; }
StringBuilder 它和 StringBuffer 同样,都是可变字符串,它用来存放元素的 char 数组没有使用 final 修饰,且,char 数组的初始大小为 16.:
StringBuilder sb = new StringBuilder(); | | public StringBuilder() { super(16); } | | AbstractStringBuilder(int capacity) { value = new char[capacity]; } | | char[] value;
其实,StringBuilder 和 StringBuffer 它们有共同的父类:
只不过 StringBuilder 不是线程安全的,在多线程环境下使用会出现数据不一致的问题。它的 append 方法并无使用 synchronized 修饰:
public StringBuilder append(String str) { super.append(str); return this; }
它经过 append 拼接字符串的字节码和 StringBuffer 的同样。
综上,
String 它是一个不可变的对象,一旦建立就不会改变,String 的相加,JVM 会调用 StringBuilder 的 append 来进行优化。
StringBuffer它是可变字符串,它是线程安全的。
StringBuilder它也是可变字符串,可是它不是线程安全的,它和 StringBuffer 有共同的父类。
因为 StringBuffer 的 append 方法有 synchronized 进行修饰,因此性能较 StringBuilder 的低,若是在单线程下,可以使用 StringBuilder。