深刻解析java.lang.String

 

   java.lang.String做爲一個最經常使用的類,出如今各類各樣的編程環境中。而其實現方式的不一樣可能形成運行效率的大大不一樣。只有深刻了解JVM對String的實現機制,才能更好的利用String。java

  • String的不變性

    String是一個final類,所以它不能被繼承,同時,用以存儲字符串內容的char value[]數組是private、final的,所以一樣不能改變它的內容。下面是String類的源碼:編程

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

...

    那麼在JVM中是怎麼對String進行更新處理的呢?答案是新建一個String類,將原引用指向這個新String類。數組

public class Main {

    public static String test(String s){
        return s.toUpperCase();
    }

    public static void main(String []args){
        String s1 = "asd";
        String s2 = test(s1);
        System.out.println("s1 is : " + s1 + " and s2 is : " + s2);
    }

}


/××
s1 is : asd and s2 is : ASD
×/

    s1做爲一個拷貝傳遞給了test函數,返回了一個新的對象引用,而s1自己並無改變。安全

    可是不變性形成的一個麻煩就是浪費空間,下降效率,以下代碼:    多線程

String s = "a" + "b" + "c";

    若是不考慮JVM所作的優化(直接生成一個字符串"abcd",而不是運行時合成;或優化使用 StringBuilder),那麼這句語句在執行的時候就會先生成一個"a",而後"a"和"b"組合生成字符串"ab",而後生成"abc",其效率可想而知(聽說java最早版本就是如此)。可是上面這個例子,因爲都是不可變的字符串,所以JVM會一次性合成"abc",進行優化。那麼合成串中包含變量呢?代碼以下:app

String a = "adsf";
String s = "a" +"b" +"c" + a;

    JVM在處理這段代碼的時候就會用到StringBuiler類。jvm

 

  • StringBuiler

    StringBuiler是一個可變的普通類。能夠調用append()的方法直接在當前串後面加上新串,而不會生成新的StringBuilder,它是final的,可是,它所實現的AbstractStringBuiler抽象類中,儲存值的char[] value不是final的,這點與String不一樣。函數

    使用相似上面String的代碼加以說明,運行後s1和s2的值同時改變了,可見s.append()並非返回一個新串,而是在引用指向的對象上動手腳。優化

public class Main {

    public static StringBuilder test(StringBuilder s){
        return s.append("d");
    }

    public static void main(String []args){
        StringBuilder s1 = new StringBuilder("abc");
        StringBuilder s2 = s1;
        test(s1);
        System.out.println("s1 is : " + s1 + " and s2 is : " + s2);
    }

}

/××
s1 is : abcd and s2 is : abcd
×/

    回到剛纔聲明String的代碼,ui

String a = "adsf";
String s = "a" +"b" +"c" + a;

    讓咱們看一看JVM是怎麼處理它的,經過javap -c Main命令查看Main的jvm彙編程序:

Compiled from "Main.java"
public class Main {
  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String adsf
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String abc
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: return
}

先說說幾個指令的含義:

  • ldc:將int、float或String型常量值從常量池中推送至棧頂
  • astore_n:將棧頂數值存入當前frame的局部變量數組中指定下標(n)處的變量中,棧頂數值出棧。
  • new:建立一個對象,而且其引用進棧
  • dup:複製棧頂數值,而且複製值進棧
  • invoke...:調用方法
  • aload_n:當前frame的局部變量數組中下標爲n的引用型局部變量進棧

    main函數中,首先直接合成字符串"adsf"放入棧頂,而後存入局部變量s。接下來建立一個StringBuiler類,初始化,接着調用append()方法將abc加入,再次調用append()加入adsf,而後調用toString()輸出。

    可見在複雜的String增加中,JVM基本上都會對String進行優化,使用StringBuiler。可是這個優化並非始終最好的,不能認爲有JVM就高枕無憂:

public static void main(String []args){
        String add[] = "q w e r t y u i o p".split(" ");
        String str = "";
        for(int i = 0;i < add.length;i++){
            str += add[i];
        }
    }

    以上代碼在一個循環中,每次爲str加上一個String,看看它的實現:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String q w e r t y u i o p
       2: ldc           #3                  // String
       4: invokevirtual #4                  // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
       7: astore_1
       8: ldc           #5                  // String
      10: astore_2
      11: iconst_0
      12: istore_3
      13: iload_3
      14: aload_1
      15: arraylength
      16: if_icmpge     46
      19: new           #6                  // class java/lang/StringBuilder
      22: dup
      23: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      26: aload_2
      27: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      30: aload_1
      31: iload_3
      32: aaload
      33: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      36: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      39: astore_2
      40: iinc          3, 1
      43: goto          13
      46: return

    能夠看到,在循環體中(13~43),每次循環,都會新建一個StringBuiler對象。顯然JVM在此處犯蠢了,所以咱們不該該寄但願於JVM進行優化,更好的方法是直接將str聲明爲StringBuiler:

public static void main(String []args){
        String add[] = "q w e r t y u i o p".split(" ");
        StringBuilder str = new StringBuilder();
        for(int i = 0;i < add.length;i++){
            str.append(add[i]);
        }
    }

    這樣就只會有一個StringBuiler對象被建立:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String q w e r t y u i o p
       2: ldc           #3                  // String
       4: invokevirtual #4                  // Method java/lang/String.split:(Ljava/lang/String;)[Ljava/lang/String;
       7: astore_1
       8: new           #5                  // class java/lang/StringBuilder
      11: dup
      12: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
      15: astore_2
      16: iconst_0
      17: istore_3
      18: iload_3
      19: aload_1
      20: arraylength
      21: if_icmpge     38
      24: aload_2
      25: aload_1
      26: iload_3
      27: aaload
      28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: pop
      32: iinc          3, 1
      35: goto          18
      38: return

    所以,瞭解String和StringBuiler的實現,對寫出高效的程序頗有必要。

  • StringBuffer

    有過了解的應該還知道有一個StringBuffer的類,它一樣實現了AbstractStringBuiler接口,大部分函數和StringBuiler功能相同,可是區別就在於這些函數前面加了synchronized關鍵字,保證線程安全。所以其運行速度要比StringBuiler稍微慢一些,可是保證了多線程下數據的準確性。這裏就再也不多說了。

相關文章
相關標籤/搜索