String、StringBuilder、StringBuffer的区别

1. 三者概述

1.1 String

(1)String是不可变的,其实就是代码中会修改字符串的方法都会返回一个新建的String对象,不会影响到原来的String对象。正因为String是不可变的,所以它对应的字符串字面量可以被共享,这应该也是字符串常量池能够存在的一个原因
(2)Java中的所有字符串字面量都被当作String的实例进行处理
(3)String的"+"运算符会转化为StringBuilder或StringBuffer进行处理

1.2 StringBuilder和StringBuffer

(1)相同点

  • 二者都是可变的,其实就是代码中返回的是this,而不是新建StringBuilder或StringBuffer对象并返回
  • 二者都继承自AbstractStringBuilder,AbstractStringBuilder.value没有被final修饰(String.value是被final修饰的),即AbstractStringBuilder.value的引用是可变的,这是扩容的前提
  • 既然二者是可变的,那么随着字符串的添加(通过append方法),容量会不足,所以会在Arrays.copyOf中新建指定大小的数组,并利用System.arraycopy进行数据的拷贝,最后AbstractStringBuilder.value会指向这个新数组

(2)不同点
二者几乎相同,不同点主要有两个:

  • StringBuilder是线程不安全的,StringBuffer是线程安全的,因为StringBuffer中除了构造方法,其余几乎所有方法都被synchronized修饰,没有被synchronized修饰在方法内最终也会调用到有synchronized的重载方法
  • StringBuffer比StringBuilder多一个toStringCache字段,用作缓存

2. 下面从字节码层面对三者字符串拼接操作的性能进行比较

2.1 String

(1)源码示例

    public static void main(String[] args) {
        String s = "";
        for (int i = 0; i < 100; i++) { 
            s += "a";
        }   
    }

(2)反编译后的字节码指令

    // 0~2:与String s = ""对应
     0: ldc           #2 // String
     2: astore_1
    // 3~34:与整个for循环对应 
    // 3~4:与int i = 0对应 
     3: iconst_0
     4: istore_2
    // 5~6:与i < 100对应 
     5: iload_2
     6: bipush        100
     8: if_icmpge     37
    // 11~30:与s += "a"对应,但转成了下面几步:
    //   step1:创建StringBuilder对象
    11: new           #3 // class java/lang/StringBuilder
    14: dup
    15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
    //   step2:将s推送至栈顶调用StringBuilder.append(s)
    18: aload_1
    19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    //   step3:将"a"推送至栈顶调用StringBuilder.append("a")
    22: ldc           #6 // String a
    24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    //   step4:调用StringBuilder.toString()
    27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    //   step5:将StringBuilder.toString()的结果存入s中
    30: astore_1
    // 31:与i++对应
    31: iinc          2, 1
    34: goto          5
    37: return

从字节码指令中可得,for循环中String的"+"运算符会转换为StringBuilder(JDK 5之前会转为StringBuffer,JDK 5及之后,会转为StringBuilder)进行处理,且每次循环都会创建新的StringBuilder,会影响性能。

2.2 StringBuilder

(1)源码示例

    public static void main(String[] args) {
        String s1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            builder.append("a");
        }
        s1 = builder.toString();
    }

(2)反编译后的字节码指令

    // 0~7:与StringBuilder builder = new StringBuilder()对应
     0: new           #2 // class java/lang/StringBuilder
     3: dup
     4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V 
     7: astore_2  
    // 8~26:与整个for循环对应   
    // 8~9:与int i = 0对应
     8: iconst_0         
     9: istore_3 
    // 10~13:与i < 100对应 
    10: iload_3
    11: bipush        100
    13: if_icmpge     29
    // 16~22:与builder.append("a")对应
    16: aload_2
    17: ldc           #4 // String a
    19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    22: pop
    // 23:与i++对应
    23: iinc          3, 1
    26: goto          10
    // 29~33:与s1 = builder.toString()对应
    29: aload_2
    30: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    33: astore_1
    34: return

2.1和2.2中完成的功能是相同的,但从字节码指令中可得,2.2中StringBuilder只创建了一次。因此,在某些字符串拼接的场景下,用StringBuilder可以提高性能。

2.3 StringBuffer

(1)源码示例

    public static void main(String[] args) {
        String s2;
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 100; i++) {
            buffer.append("a");
        }
        s2 = buffer.toString();
    }

(2)反编译后的字节码指令
与2.2(2)中的字节码指令完全一致(不同点是常量池中的StringBuilder会变成StringBuffer),但由于StringBuffer的方法由synchronized修饰,因此,它的拼接操作在速度上比StringBuilder慢,但比String快。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容