java9引入了StringConcatFactory,产生了很多变化,我们先来看JDK1.8
JDK1.8
代码示例
public void test() {
String a = "a" + "b";
// 单行+
String c = "c" + a + a;
// 单行append
StringBuilder builder = new StringBuilder("c");
builder.append(a).append(a);
// 多行+
c += a;
c += a;
// for循环append
for (int i = 1; i <= 100000; i++) {
builder.append(a);
}
// for循环+
for (int i = 1; i <= 100000; i++) {
String x = c + a;
}
for (int i = 1; i <= 100000; i++) {
c += a;
}
}
字节码
// class version 52.0 (52)
// access flags 0x21
public class com/zhangyue/momr/api/service/Test {
// compiled from: Test.java
// access flags 0x1
public ()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
RETURN
L1
LOCALVARIABLE this Lcom/zhangyue/momr/api/service/Test; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public test()V
L0
LINENUMBER 9 L0
LDC "ab"
ASTORE 1
L1
LINENUMBER 12 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
LDC "c"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
L2
LINENUMBER 15 L2
NEW java/lang/StringBuilder
DUP
LDC "c"
INVOKESPECIAL java/lang/StringBuilder. (Ljava/lang/String;)V
ASTORE 3
L3
LINENUMBER 16 L3
ALOAD 3
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L4
LINENUMBER 19 L4
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
L5
LINENUMBER 20 L5
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
L6
LINENUMBER 23 L6
ICONST_1
ISTORE 4
L7
FRAME FULL [com/zhangyue/momr/api/service/Test java/lang/String java/lang/String java/lang/StringBuilder I] []
ILOAD 4
LDC 100000
IF_ICMPGT L8
L9
LINENUMBER 24 L9
ALOAD 3
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
POP
L10
LINENUMBER 23 L10
IINC 4 1
GOTO L7
L8
LINENUMBER 28 L8
FRAME CHOP 1
ICONST_1
ISTORE 4
L11
FRAME APPEND [I]
ILOAD 4
LDC 100000
IF_ICMPGT L12
L13
LINENUMBER 29 L13
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 5
L14
LINENUMBER 28 L14
IINC 4 1
GOTO L11
L12
LINENUMBER 31 L12
FRAME CHOP 1
ICONST_1
ISTORE 4
L15
FRAME APPEND [I]
ILOAD 4
LDC 100000
IF_ICMPGT L16
L17
LINENUMBER 32 L17
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder. ()V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
L18
LINENUMBER 31 L18
IINC 4 1
GOTO L15
L16
LINENUMBER 34 L16
FRAME CHOP 1
RETURN
L19
LOCALVARIABLE i I L7 L8 4
LOCALVARIABLE i I L11 L12 4
LOCALVARIABLE i I L15 L16 4
LOCALVARIABLE this Lcom/zhangyue/momr/api/service/Test; L0 L19 0
LOCALVARIABLE a Ljava/lang/String; L1 L19 1
LOCALVARIABLE c Ljava/lang/String; L2 L19 2
LOCALVARIABLE builder Ljava/lang/StringBuilder; L3 L19 3
MAXSTACK = 3
MAXLOCALS = 6
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 37 L0
NEW com/zhangyue/momr/api/service/Test
DUP
INVOKESPECIAL com/zhangyue/momr/api/service/Test. ()V
INVOKEVIRTUAL com/zhangyue/momr/api/service/Test.test ()V
L1
LINENUMBER 38 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
}
- 首先String a = "a" + "b";可以看到LDC "ab",证明编译器在编译阶段就直接做了优化
- String c = "c" + a + a;可以看到优化成了一个StringBuilder,然后做了3次append
- 当你把多个+写在同一行拼接多次时,是一个StringBuilder做多次append(加号的数量+1);当写成多行的时候,会new多个StringBuilder
- 在for循环中使用+时,每次循环都会new一个StringBuilder
for (int i = 1; i <= 100000; i++) {
builder.append(a);
}
for (int i = 1; i <= 100000; i++) {
String x = c + a;
}
for (int i = 1; i <= 100000; i++) {
c += a;
}
第三个for循环跟前两个相比性能差距巨大,但是看字节码长得都差不多,这是为什么呢?
第二个和第三个for循环里的代码都等价于new StringBuilder().append(c).append(a);但是最后一个for循环的append(c)里的c是在不断变大的,append底层调用的是System.arraycopy,在每次循环都append一个比较大的字符串,性能是很差的
测试单行StringBuilder和+拼接的运行时间
public void test() {
String a = "a" + "b";
String c = "c" + a;
long t3 = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
StringBuilder builder = new StringBuilder(c);
builder.append(a).append(a);
}
long t4 = System.currentTimeMillis();
System.out.println(t4 - t3);
long t1 = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
String x = c + a + a;
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
结果可以看到基本是一样的
24
19
测试for循环中append和+的运行时间
public void test() {
String a = "a" + "b";
String c = "c" + a;
StringBuilder builder = new StringBuilder(c);
long t3 = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
builder.append(a);
}
long t4 = System.currentTimeMillis();
System.out.println(t4 - t3);
long t1 = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
c += a;
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}
结果可以看到+的运行时间特别的长
9
7722