String
、StringBuffer
、StringBuilder
的区别
可变性
String
是不可变的(详细原因后面分析)StringBuilder
与StringBuffer
都继承自AbstractStringBuilder
类,在该类中也是使用char
数组来保存字符串(JDK9 之后改为了使用byte
数组,后面会介绍),对于这个数组,在AbstractStringBuilder
中没有使用private
和final
的关键字修饰(在String
类当中有private
和final
修复,不过这其实并不是导致String
不可变的原因),同时AbstractStringBuilder
提供了很多修改字符串的方法,比如append()
、insert()
、delete()
、replace()
等。
public AbstractStringBuilder append(StringBuffer sb) { |
线程安全性
String
中的对象是不可变的,也就可以理解为每一个String
被创建之后就被作为常量存储,因此是线程安全的AbstractStringBuilder
是StringBuilder
和StringBuffer
的公共负累,提供了一些字符串的基本操作,对于StringBuilder
和StringBuffer
来说:StringBuilder
对于这些方法没有加同步锁(synchorized),是非线程安全的StringBuffer
对于这些方法加了同步锁(synchorized),是线程安全的。
性能
- 对于
String
来说,每次对其进行改变实际上是会产生一个新的String
对象,然后将指针指向了新的String
对象 StringBuffer
每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用,但关于StringBuffer
需要注意的有两点:StringBuffer
是线程安全的,因此在性能上没有StringBuilder
出色StringBuffer
提供了toStringCache
的一个成员变量用于缓存toString()
方法的字符串,可能会占据更多的空间(但同时也会提升toString()
方法的性能)
StringBuilder
与StringBuffer
同理,不同点在于其没有保证线程安全,性能较好但在多线程场景下需要考虑线程安全问题;同时由于其没有提供toString()
方法的缓存机制,因此其所占的堆大小可能会比StringBuffer
更少。
关于
StringBuffer
的toStringCache
:
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
toStringCache
的作用是用作toString()
方法的最后一个缓存,并在 StringBuffer 被修改(modified)时重新变为 null。
对于三者使用的总结
- 操作少量的数据:适用
String
- 单线程操作字符串缓冲区下操作大量数据:适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据:适用
StringBuffer
String
为什么是不可变的
String
类中使用 final
和 private
关键字来修饰字符数组保存字符串:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { |
我们知道被 final
关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final
关键字修饰的数组保存字符串并不是 String
不可变的根本原因,因为这个数组保存的字符串是可变的(final
修饰引用类型变量的情况)。
String
真正不可变的原因如下:
- 保存字符串的数组被
final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。 String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。
在 Java 9 之后,
String
、StringBuilder
与StringBuffer
的实现改用byte
数组存储字符串
sb
public final class String implements java.io.Serializable,Comparable<String>, CharSequence {
// @Stable 注解表示变量最多被修改一次,称为“稳定的”。
private final byte[] value;
}
abstract class AbstractStringBuilder implements Appendable,CharSequence {
byte[] value;
}新版的 String 其实支持两个编码方案: Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。
JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。官方介绍
字符串拼接用 “+” 还是用 StringBuilder
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
String str1 = "he"; |
上面的代码对于的字节码如下
可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 StringBuilder
调用 append()
方法实现的,拼接完成之后调用 toString()
得到一个 String
对象 。
不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个
StringBuilder
以复用,会导致创建过多的StringBuilder
对象。
String equals()
String
的 equals()
方法是被重写过的,比较的是 String
字符串的值是否相等。
字符串常量池
// todo