简单区分
String: 字符串,一旦创建不可更改
StringBuffer:字符串构造器,可以修改字符串内容,线程安全
StringBuilder:同StringBuffer,线程不安全
String由于创建之后不可更改,所以改变String的操作其实是新生成了一个新的String对象,所以频繁改变的字符串建议使用StringBuffer/StringBuider。
StringBuffer VS StringBuilder
StringBuilder是在Java 5中提出的,跟StringBuffer有相同的接口,但不是线程安全的。
在Java 8中,分析源码可以看到,两者父类相同,实现了同样的接口。摘抄源码代码段如下:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
有兴趣的同学可以看一下这两个类,实现基本差不多,StringBuffer为了保证线程安全在相关函数上加了synchronized
关键字。
我在阅读源码时发现StringBuffer有一个变量toStringCache
,而StringBuilder没有,查阅了一下资料,可能是历史原因。参见:https://stackoverflow.com/questions/46294579/why-stringbuffer-has-a-tostringcache-while-stringbuilder-not
String 拼接的编译优化
从Java 5开始,Java就对String字符串的+操作进行了优化,主要有两种优化。
- 常量的拼接
如果拼接内容为常量,比如:String a = "Hello " + "World";
应该被直接优化为了String a = "Hello World;
。 - 需要运行时确定内容的拼接
拼接内容如果有非 常量,会将+
优化为StringBuilder的append操作
如下:
public class StringTest {
public static void main(String[] args) {
String a = "Hello ";
String b = a + "World";
String c = b + "!";
}
}
优化后代码类似于:
public class StringTest {
public static void main(String[] args) {
String a = "Hello ";
String b = new StringBuilder().append(a).append("World").toString();
String c = new StringBuilder().append(b).append("!").toString();
}
}
有兴趣的同学可以创建一个简单类,编译上面两段代码,编译出的class文件应该是一样的。
直接编译StringTest类javac StringTest.java
查看class文件:javap -verbose StringTest.class
我这里贴一个优化前代码编译出的代码片段
关于常量拼接有一个比较好的文章,直接贴链接:https://bbs.csdn.net/topics/300264665
虽然编译器有一定程度的优化,但是在字符串变动较多时,还是要要使用StringBuffer/StringBuilder,如上面的例子,其实哪怕优化后也是新建了两个StringBuilder,在循环中这种情况更为明显。
使用总结
- 字符串为常量的拼接,可以直接使用String
- 字符串一经定义极少修改,使用String
- 字符串变动较多使用StringBuffer/StringBuilder
- 单线程(不要求线程安全)使用StringBuilder,多线程(要求线程安全)使用StringBuffer。