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 耗时:6msProcess finished with exit code 0
所以在需要进行大量的字符串拼接时应该尽量使用StringBuffer和StringBuilder,具体场景看是否需要线程安全。
本文地址:https://blog.csdn.net/weixin_43260413/article/details/109564381
上一篇: Java各种数据类型互相转换
推荐阅读