String StringBuffer 和 StringBuilder 的区别
目录
可变性
String
类是不可变的,其原因如下:
String
不可变原因:
- 在
String
类中保存字符串的字符数组是被final
修饰且为私有的,并且String
类没有提供/暴露修改字符串的方法。String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char value[]; //... }
而StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用 final
和 private
关键字修饰,最关键的是这个 AbstractStringBuilder
类还提供了很多修改字符串的方法比如 append
方法。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
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;
}
//...
}
线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是 StringBuilder
与 StringBuffer
的公共父类,定义了一些字符串的基本操作,如 expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
性能
每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象。StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
补充
String 类的使用
在使用 String
类型作 +
运算时,分两种情况:
-
两个字符串常量作为操作数:
String str = "a" + "bc"; // str = "abc" System.out.println(str == "abc") // true
这种情况
Java
中有常量优化机制,先在常量池中创建"a"
、"bc"
和"abc"
,之后会由编译器优化,只在常量池中保存"abc"
,再将str
指向"abc"
。所以str == "abc"
结果为true
。 -
一个或两个都为
String
类对象:String str = new String("a") + "bc"; // str = new StringBuilder("a").append("bc").toString(); 或 new StringBuffer("a").append("bc").toString(); // String str = new String("a") + new String("bc"); // 同理 System.out.println(str == "abc") // false
这种情况,
+
运算拼接的原理是由StringBuilder
或者StringBuffer
类和里面的append
方法实现拼接,然后调用toString()
把拼接的对象转换成字符串对象,最后把得到字符串对象的地址赋值给变量。值得注意的是,
StringBuilder
和StringBuffer
类的toString()
方法会创建一个新的String
变量,其源码:@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
所以,第二种情况一共创建了
"a"
和"bc"
两个常量对象及三个String
变量,一共 5 个String
对象,注意new String("a")
会被JVM
回收;而第一种情况则创建了"a"
、"bc"
和"abc"
三个常量对象,同样注意在编译器优化后只存在"abc"
一个常量对象。
StringBuilder 和 StringBuffer 使用
在 IDEA
下,某些方法中的没有对 StringBuffer
进行多线程操作时,使用了 StringBuffer
进行字符串操作,IDEA
默认会提示 'StringBuffer sb' may be declared as 'StringBuilder'
,而且会建议替换成 StringBuilder
,因为 StringBuilder
的效率比 StringBuffer
高,IDEA
认为你没有在多线程环境下,使用 StringBuilder
可能是更好的选择。
那 IDEA 的判断是否准确?基本上准确,不过限定于方法内,主要原因如下:
-
如果
StringBuffer
是成员变量,无论如何IDEA
都不会提示'StringBuffer stringBuffer' may be declared as 'StringBuilder'
,因为IDEA
无法保证StringBuffer
不会在这个类的外部不被其他方法进行并发,即便StringBuffer
是被private
修饰的,没有提供对外暴露的方法,但是依然可以通过反射拿到这个StringBuffer
,进行并发操作。 -
如果
StringBuffer
是局部变量,当在这个局部范围内(方法内)没有对StringBuffer
的方法进行并发,并且没有将StringBuffer
传递给别的方法,IDAE
就会提示'StringBuffer stringBuffer' may be declared as 'StringBuilder'
,因为在这种情况下,外部无法拿到局部的StringBuffer
。
所以在方法内出现这种提示时,确实没在多线程环境下使用 StringBuffer
,将来也不会修改为多线程,可以考虑直接替换为 StringBuilder
,当然这仍需要结合项目的情况做选择。