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,当然这仍需要结合项目的情况做选择。



京公网安备 11010502036488号