String、StringBuilder和StringBuffer的区别

String、StringBuilder和StringBuffer的区别

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

String

查看String类的继承关系:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence 

final修饰的class是不可以被继承的。

其次String内部存储字符串的结构是char数组,而且是final修饰的,因此在进行字符串的拼接时效率较低。

每次对字符串修改,底层都会重新开辟新的堆内存空间,这样会开辟很多个空间地址,造成浪费。

 /** The value is used for character storage. */
    private final char value[];

分析String对象用+进行字符串拼接时的底层执行过程:

首先写一个简单的java文件:

public class Test{

	public static void main(String[] args){
		String a = "a";
		String b = a+"b";
		System.out.println(b);
	}

}

然后用javap -verbose Test进行反编译,得到以下内容:

Last modified 2020-11-8; size 585 bytes
  MD5 checksum a00e5acaccc9ac98c689faae92229e7d
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#20        // java/lang/Object."<init>":()V
   #2 = String             #21            // a
   #3 = Class              #22            // java/lang/StringBuilder
   #4 = Methodref          #3.#20         // java/lang/StringBuilder."<init>":()V
   #5 = Methodref          #3.#23         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #6 = String             #24            // b
   #7 = Methodref          #3.#25         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
   #9 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = Class              #30            // Test
  #11 = Class              #31            // java/lang/Object
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               main
  #17 = Utf8               ([Ljava/lang/String;)V
  #18 = Utf8               SourceFile
  #19 = Utf8               Test.java
  #20 = NameAndType        #12:#13        // "<init>":()V
  #21 = Utf8               a
  #22 = Utf8               java/lang/StringBuilder
  #23 = NameAndType        #32:#33        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #24 = Utf8               b
  #25 = NameAndType        #34:#35        // toString:()Ljava/lang/String;
  #26 = Class              #36            // java/lang/System
  #27 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;
  #28 = Class              #39            // java/io/PrintStream
  #29 = NameAndType        #40:#41        // println:(Ljava/lang/String;)V
  #30 = Utf8               Test
  #31 = Utf8               java/lang/Object
  #32 = Utf8               append
  #33 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #34 = Utf8               toString
  #35 = Utf8               ()Ljava/lang/String;
  #36 = Utf8               java/lang/System
  #37 = Utf8               out
  #38 = Utf8               Ljava/io/PrintStream;
  #39 = Utf8               java/io/PrintStream
  #40 = Utf8               println
  #41 = Utf8               (Ljava/lang/String;)V
{
  public Test();
    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 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // String a
         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 b
        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: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        26: aload_2
        27: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: return
      LineNumberTable:
        line 4: 0
        line 5: 3
        line 6: 23
        line 7: 30
}
SourceFile: "Test.java"

会发现底层还是调用了StringBuilder的append()方法。

StringBuilder

查看StringBuilder类的继承关系:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

存储字符串的底层结构

 /**
 * The value is used for character storage.
 */
char[] value;

StringBuffer

查看StringBuffer类的继承关系:

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence

发现StringBuffer与StringBuilder继承的是同一个抽象类

StringBuffer是一个字符串缓冲区,如果需要频繁的对字符串进行拼接时,建议使用StringBuffer。

StringBuffer的底层是char数组,与StringBuilder一样,继承自AbstractStringBuilder

 /**
 * The value is used for character storage.
 */
char[] value;

在使用时如果数组容量不够了,则会通过数组的拷贝对数组进行扩容,所以在使用StringBuffer时最好预测并手动初始化长度,这样能够减少数组的拷贝,从而提高效率。

StringBuilder与StringBuffer的方法基本一致,最大的区别是StringBuffer的普通方法都有synchronized关键字,也就是线程安全的。

总结

如果需要对字符串进行频繁拼接的话,建议使用StringBuffer或者StringBuilder,两者的使用方法一致,下面以StringBuffer为例说明。

拼接字符串的四种方法:
String类的:+、concat()方法

​StringBuilder类:append()方法

SringBuffer类:append()方法

测试这四种方法的效率:

/**
 * 字符串拼接的各种方式性能对比
 */
public class StringConcatTest {
    public static void testAdd(int num){
        long start = System.currentTimeMillis();
        String str = "";
        for(int i = 0; i < num; i++){
            str += i;
        }
        System.out.println("字符串拼接使用 + 耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void testConcat(int num){
        long start = System.currentTimeMillis();
        String str = "";
        for(int i = 0; i < num; i++){
            str.concat(String.valueOf(i));
        }
        System.out.println("字符串拼接使用 concat 耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void testStringBuffer(int num){
        long start = System.currentTimeMillis();
        StringBuffer stringBuffer = new StringBuffer();
        for(int i = 0; i < num; i++){
            stringBuffer.append(String.valueOf(i));
        }
        stringBuffer.toString();
        System.out.println("字符串拼接使用 StringBuffer 耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void testStringBuilder(int num){
        long start = System.currentTimeMillis();
        StringBuilder stringBuilder = new StringBuilder();
        for(int i = 0; i < num; i++){
            stringBuilder.append(String.valueOf(i));
        }
        stringBuilder.toString();
        System.out.println("字符串拼接使用 StringBuilder 耗时:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void main(String[] args) {
        int num = 100000;
        //+号拼接
        testAdd(num);
        //concat方法拼接
        testConcat(num);
        //StringBuffer的append方法拼接
        testStringBuffer(num);
        //StringBuilder的append方法拼接
        testStringBuilder(num);
    }
}

执行结果如下:

字符串拼接使用 + 耗时:18466ms
字符串拼接使用 concat 耗时:11ms
字符串拼接使用 StringBuffer 耗时:6ms
字符串拼接使用 StringBuilder 耗时:6ms

Process finished with exit code 0

所以在需要进行大量的字符串拼接时应该尽量使用StringBuffer和StringBuilder,具体场景看是否需要线程安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值