String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
String 不可变性
String是一个final类,代表不可变的字符序列。
字符串是常量,用双引号引起来表示。 它们的值在创建之后不能更改。
String对象的字符内容是存储在一个字符数组value[]中的。
String慢的原因
String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
StringBuffer
java.lang.StringBuffer代表可变的字符序列, JDK1.0中声明,可以对字符
串内容进行增删,此时不会产生新的对象。很多方法与String相同。 作为参数传递时,方法内部可以改变值。
StringBuffer类
StringBuffer类不同于String,其对象必须使用构造器生成。
线程安全
StringBuffer是线程安全的,StringBuilder是线程不安全的。
那么StringBuilder不安全在哪里?在想这个问题前,我们要知道StringBuffer和StringBuilder的内部实现和String类是一样的,都是通过一个char数组存储字符串,不同的是String类的char数组是final修饰的,是不可变的;但是StringBuffer和StringBuilder的char数组是可变的。
Stringbuffer的append方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
stringbulider的append方法
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
明显的看到buffer的里面synchronized关键字,方法是线程安全的,但是也带来了性能的损失,造成性能下降。
StringBuffer扩容
StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过capacity()方法来获取当前实体的实际容量。
哪里来的长度是16呢?
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
人家源码写的是初始长度是16。
扩容算法:
使用append()方法在字符串后面追加东西的时候,如果长度超过了该字符串存储空间大小了就需要进行扩容:构建新的存储空间更大的字符串,将旧的复制过去
/**
* For positive values of {@code minimumCapacity}, this method
* behaves like {@code ensureCapacity}, however it is never
* synchronized.
* If {@code minimumCapacity} is non positive due to numeric
* overflow, this method throws {@code OutOfMemoryError}.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by the same amount + 2 if
* that suffices.
* Will not return a capacity greater than {@code MAX_ARRAY_SIZE}
* unless the given minimum capacity is greater than that.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero or
* greater than Integer.MAX_VALUE
*/
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;
}
看上面的代码知道
StringBuffer的扩容实际上就是新建了一个数组,将原来旧数组的内容复制到新数组,扩容机制根据当前数组长度的2倍+2和新增加字符串长度+原有数组长度进行比较,如果前者小于后者,那么扩容后的长度就是后者,如果前者大于后者那么扩容后的数组长度就是前者,每次append或者insert会再次进行比较.
问题又来了,这个+2是为什么呢
引用知乎上一个回答
请注意传入参数int,意味着这里传入参数可以是0,那么在参数是0的情况下,0<<1运算结果也是0,那么在初始化数组的时候必然会报错,所以作为设计的安全性考虑,这里防止出现报错,选择了+2