学习Java的过程中,我们一定听过String是不可变的,String也是Java中最基本最常使用的一个类,Java在运行时保存了一个字符串池,也使String成为一个很特殊的类。
String类的不可变性
- 打开JDK中String类的源码,其中对于值的定义如下
private final char value[];
其中保存String值是一个char数组,而此数组是使用final定义的,所以无法修改
StringBuilder和StringBuffer
StringBuilder和StringBuffer都继承自AbstractStringBuilder类,而此类对于值的定义如下
char[] value;
所以StringBuilder和StringBuffer可以对值进行改变。
String类操作值
String对象进行改变值的操作时,会在StringPool中新建一个字符串对象,将改变值操作的引用指向新的地址。所有操作的字符串都是一个常量,保存在StringPool中,新建String对象的时候,先去StringPool寻找是否有此字符串存在,若存在,直接将对象引用指向此地址,若不存在则添加进来。这样不同的字符串变量如果是一样的值的话,则可以实现不同对象使用同一块地址,节省空间。所以String类一定要是不可变的,若可变,则StringPool无法实现,不同的引用指向了同一块内存空间,但是被其中一个引用修改了此内存空间的值,则影响其他引用的使用。
StringBuffer和StringBuilder的容量问题
StringBuffer和StringBuilder都可以对值进行操作,而值为char的数组,自然存在容量问题。
StringBuffer的构造方法:
public StringBuffer() {
super(16);
}
public StringBuffer(int capacity) {
super(capacity);
}
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
StringBuffer的构造方法有四个重载,没有参数默认容量16,使用int作为构造函数参数则以传入的int值为准,以String为参数则默认容量为String的长度+16,CharSequence 与String方法类似。StringBuilder和StringBuffer类似。
在append的时候要去校验一长度是否足够
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;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {//如果容量不足
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));//扩容,复制
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;//扩容
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
从源码可以看出,在append的时候先去判断容量是否足够,如果不够的话,进行扩容,扩容策略为当前长度的2倍加2,如果还不够,则直接扩到需要的长度然后进行Array类的CopyOf操作。
三者的线程安全性
String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
三者操作建议
操作少量的数据 = String
单线程操作字符串缓冲区下操作大量数据 = StringBuilder
多线程操作字符串缓冲区下操作大量数据 = StringBuffer