🔍
📢

String、StringBuffer、StringBuilder类区别

        String、StringBuffer、StringBuilder是常用的Java字符串操作类,对于他们的区别大家也都能耳熟能详,但底层到底是怎样实现的呢?今天就再深入分析下这三种字符串操作的区别、各自的原理及使用场景。

 

一、String

       先来看一下JDK中String中的部分源码:


复制代码
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);
    }    
    
    ...
}
复制代码

        可以看到String类、以及value都是final类型的,这样就表明String是无法被继承的,value是无法被改写的。当通过String的构造函数初始化新的String对象时,也只是根据传入的引用对象的value和hashcode进行了赋值。看下面的例子:

复制代码
public class StringTest {

    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        String Str3 = new String("abc");
    }
}
复制代码

       执行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"
复制代码

        可以看到对于相同的字符串“abc”的引用都是相同的(对于常量池中的相同位置),这样能够节省内存空间,但是缺点就是对于频繁的字符串拼接操作,会造成内存空间的浪费。(需要注意的是这种字符串的拼接操作,从JDK8 开始,会自动被编译成StringBuilder,是不是很666^_^,但还是建议不通过JDK途径去自动转。)看下面的代码:

复制代码
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";
    }
}
复制代码

       然后再通过javap看下class文件:

复制代码
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"
复制代码

二、StringBuilder

       也是先来看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;
    }

    ...
}
复制代码

        可以看到StringBuilder的value是个char数组,(当然从JDK9开始,value从char数组变成了byte数组)。每次append时都是通过调用native的System.arraycopy实现的(在getChars中调用的)。

三、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;
    }
    ...
}
复制代码

        和StringBuilder一样,都是用了char数组保存value,append也是调用了AbstractStringBuilder的append方法。区别只是在于char数组加了transient关键字,以及方法上加了synchronized方法。

       综上所述,String、StringBuilder、StringBuffer的使用场景如下:

       当处理定长字符串时,建议用String;

       当处理变长字符串时,并且是单线程环境时,建议用StringBuilder;

       当处理变长字符串时,并且是多线程环境时,建议用StringBuffer。

 

    原文  https://www.cnblogs.com/jpcflyer/p/9280501.html