zoukankan      html  css  js  c++  java
  • 深入理解String, StringBuffer, StringBuilder的区别(基于JDK1.8)

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

           请尊重作者劳动成果,转载请标明原文链接:

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

    一、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);
        }    
        
        ...
    }
    View Code

            可以看到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");
        }
    }
    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途径去自动转。)看下面的代码:

    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文件:

    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

    二、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;
        }
    
        ...
    }
    View Code

            可以看到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;
        }
        ...
    }
    View Code

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

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

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

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

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

  • 相关阅读:
    HDU-1102 Constructing Roads ( 最小生成树 )
    POJ-1287 Networking ( 最小生成树 )
    HDU-1272 小希的迷宫 ( 并查集 )
    Java基本数据类型、关键字
    观察者模式
    Android系统启动过程分析
    Activity启动过程源码分析(Android 8.0)
    Okhttp解析—Okhttp概览
    Okhttp解析—Interceptor详解
    Okhttp源码分析--基本使用流程分析
  • 原文地址:https://www.cnblogs.com/jpcflyer/p/9280501.html
Copyright © 2011-2022 走看看