深刻理解String, StringBuffer, StringBuilder的區別(基於JDK1.8)

        String、StringBuffer、StringBuilder都是JAVA中經常使用的字符串操做類,對於他們的區別你們也都能耳熟能詳,但底層究竟是怎樣實現的呢?今天就再深刻分析下這三種字符串操做的區別、各自的原理及使用場景。html

       請尊重做者勞動成果,轉載請標明原文連接:java

       http://www.javashuo.com/article/p-wcncfcpn-bx.html數組

1、String多線程

       先來看一下JDK中String中的部分源碼:app

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; private int hash; // Default to 0

     public String() { this.value = new char[0]; } public String(String original) { this.value = original.value; this.hash = original.hash; } public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } ... }
View Code

        能夠看到String類、以及value都是final類型的,這樣就代表String是沒法被繼承的,value是沒法被改寫的。當經過String的構造函數初始化新的String對象時,也只是根據傳入的引用對象的value和hashcode進行了賦值。看下面的例子:ide

public class StringTest { public static void main(String[] args) { String str1 = "abc"; String str2 = "abc"; String Str3 = new String("abc"); } }
Vew Code

       執行javac StringTest.java後,經過javap -v StringTest.class看下生成的class文件:函數

Classfile /C:/Users/jiang/workspace/test/src/test/StringTest.class Last modified 2018-7-8; size 363 bytes MD5 checksum f7e4243b0247fb20c5a336d4ba0a580f Compiled from "StringTest.java"
public class test.StringTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = String             #16            // abc
   #3 = Class              #17            // java/lang/String
   #4 = Methodref          #3.#18         // java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #19            // test/StringTest
   #6 = Class              #20            // java/lang/Object
   #7 = Utf8               <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8               ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 StringTest.java #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Utf8 abc #17 = Utf8               java/lang/String #18 = NameAndType        #7:#21         // "<init>":(Ljava/lang/String;)V
  #19 = Utf8               test/StringTest #20 = Utf8               java/lang/Object #21 = Utf8               (Ljava/lang/String;)V { public test.StringTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1
         0: aload_0 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return LineNumberTable: line 3: 0

  public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1
         0: ldc           #2                  // String abc
         2: astore_1 3: ldc           #2                  // String abc
         5: astore_2 6: new           #3                  // class java/lang/String
         9: dup 10: ldc           #2                  // String abc
        12: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        15: astore_3 16: return LineNumberTable: line 6: 0 line 7: 3 line 8: 6 line 9: 16 } SourceFile: "StringTest.java"
View Code

        能夠看到對於相同的字符串「abc」的引用都是相同的(對於常量池中的相同位置),這樣可以節省內存空間,可是缺點就是對於頻繁的字符串拼接操做,會形成內存空間的浪費。(須要注意的是這種字符串的拼接操做,從JDK8 開始,會自動被編譯成StringBuilder,是否是很666^_^,但仍是建議不經過JDK途徑去自動轉。)看下面的代碼:ui

public class StringTest { public static void main(String[] args) { String str1 = "abc"; //String str2 = "abc"; //String str3 = new String("abc");
        String str4 = str1 + "d"; String str5 = str4 + "e"; } }
View Code

       而後再經過javap看下class文件:this

Classfile /C:/Users/jiang/workspace/test/src/test/StringTest.class Last modified 2018-7-8; size 493 bytes MD5 checksum c02bd18ed3ecbe46f9859bf5e272c663 Compiled from "StringTest.java"
public class test.StringTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref          #10.#19        // java/lang/Object."<init>":()V
   #2 = String             #20            // abc
   #3 = Class              #21            // java/lang/StringBuilder
   #4 = Methodref          #3.#19         // java/lang/StringBuilder."<init>":()V
   #5 = Methodref          #3.#22         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #6 = String             #23            // d
   #7 = Methodref          #3.#24         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = String             #25            // e
   #9 = Class              #26            // test/StringTest
  #10 = Class              #27            // java/lang/Object
  #11 = Utf8               <init> #12 = Utf8 ()V #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 main #16 = Utf8               ([Ljava/lang/String;)V #17 = Utf8 SourceFile #18 = Utf8 StringTest.java #19 = NameAndType        #11:#12        // "<init>":()V
  #20 = Utf8 abc #21 = Utf8               java/lang/StringBuilder #22 = NameAndType        #28:#29        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #23 = Utf8 d #24 = NameAndType        #30:#31        // toString:()Ljava/lang/String;
  #25 = Utf8 e #26 = Utf8               test/StringTest #27 = Utf8               java/lang/Object #28 = Utf8 append #29 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder; #30 = Utf8 toString #31 = Utf8               ()Ljava/lang/String; { public test.StringTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1
         0: aload_0 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return LineNumberTable: line 3: 0

  public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1
         0: ldc           #2                  // String abc
         2: astore_1 3: new           #3                  // class java/lang/StringBuilder
         6: dup 7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        10: aload_1 11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        14: ldc           #6                  // String d
        16: invokevirtual #5                  // 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: new           #3                  // class java/lang/StringBuilder
        26: dup 27: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
        30: aload_2 31: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        34: ldc           #8                  // String e
        36: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        39: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        42: astore_3 43: return LineNumberTable: line 6: 0 line 9: 3 line 10: 23 line 11: 43 } SourceFile: "StringTest.java"
View Code

2、StringBuilderspa

       也是先來看StringBuilder的源碼:

public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence { public StringBuilder() { super(16); } public StringBuilder(String str) { super(str.length() + 16); append(str); } public StringBuilder append(String str) { super.append(str); return this; } ... } abstract class AbstractStringBuilder implements Appendable, CharSequence { char[] value; int count; AbstractStringBuilder(int capacity) { value = new char[capacity]; } public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } ... }
View Code

        能夠看到StringBuilder的value是個char數組,(固然從JDK9開始,value從char數組變成了byte數組)。每次append時都是經過調用native的System.arraycopy實現的(在getChars中調用的)。

3、StringBuffer

S       tringBuffer的源碼以下:

 public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence { private transient char[] toStringCache; public StringBuffer() { super(16); } public StringBuffer(String str) { super(str.length() + 16); append(str); } public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; } ... }
View Code

        和StringBuilder同樣,都是用了char數組保存value,append也是調用了AbstractStringBuilder的append方法。區別只是在於char數組加了transient關鍵字,以及方法上加了synchronized方法。

       綜上所述,String、StringBuilder、StringBuffer的使用場景以下:

       當處理定長字符串時,建議用String;

       當處理變長字符串時,而且是單線程環境時,建議用StringBuilder;

       當處理變長字符串時,而且是多線程環境時,建議用StringBuffer。

相關文章
相關標籤/搜索