编译期优化

编译器在编译期会针对字符串常量叠加得到固定值,字符串常量包括"hello"或用fianl修饰的变量,编译器认为这些常量是不可变的

编译器优化String常量连接

示例一

String str = "hello" + "java" + 1;
// 编译期编译器会直接编译为"hellojava1"
#2 = String             #21            // hellojava1
#21 = Utf8               hellojava1

示例二

public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 }
output ==> s等于t,它们是同一个对象

说明:A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";

示例三

public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 }
output ==> s不等于t,它们不是同一个对象

A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了

循环内String加操作

  • 性能较低的代码:
public void  implicitUseStringBuilder(String[] values) {
  String result = "";
  for (int i = 0 ; i < values.length; i ++) {
      result += values[i];
  }
  System.out.println(result);
}

编译后的字节码:

public void implicitUseStringBuilder(java.lang.String[]);
  Code:
     0: ldc           #11                 // String
     2: astore_2
     3: iconst_0
     4: istore_3
     5: iload_3
     6: aload_1
     7: arraylength
     8: if_icmpge     38
    11: new           #5                  // class java/lang/StringBuilder
    14: dup
    15: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
    18: aload_2
    19: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    22: aload_1
    23: iload_3
    24: aaload
    25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    28: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    31: astore_2
    32: iinc          3, 1
    35: goto          5
    38: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
    41: aload_2
    42: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    45: return

其中8: if_icmpge 38 和35: goto 5构成了一个循环;

8: if_icmpge 38的意思是如果(i < values.length的相反结果)成立,则跳到第38行(System.out)。

35: goto 5则表示直接跳到第5行。

但是这里面有一个很重要的就是StringBuilder对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个StringBuilder对象,这样明显性能较低

  • 性能较高的代码
public void explicitUseStringBuider(String[] values) {
  StringBuilder result = new StringBuilder();
  for (int i = 0; i < values.length; i ++) {
      result.append(values[i]);
  }
}
public void explicitUseStringBuider(java.lang.String[]);
  Code:
     0: new           #5                  // class java/lang/StringBuilder
     3: dup
     4: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
     7: astore_2
     8: iconst_0
     9: istore_3
    10: iload_3
    11: aload_1
    12: arraylength
    13: if_icmpge     30
    16: aload_2
    17: aload_1
    18: iload_3
    19: aaload
    20: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    23: pop
    24: iinc          3, 1
    27: goto          10
    30: return

从上面可以看出,13: if_icmpge 3027: goto 10构成了一个loop循环,而0: new #5位于循环之外,所以不会多次创建StringBuilder.

注意:循环体中需要尽量避免隐式或者显式创建StringBuilder

results matching ""

    No results matching ""