String,StringBuffer和StringBuilder之间的区别,应用及底层实现已经是老生常谈了.从开始接触java到现在,只要是要复习或者是准备面试,这个题就是必须要经历的.可以说是众里寻他千百度,蓦然回首,他依旧在那等着你啊.
这里,我就聊一下我对这3个类的理解~
**
**
首先,String这个老大哥底层是维持的是 final 修饰过的 private 权限的 char型数组.
这个final就很厉害呀,他修饰过之后,这个char型数组就成为了一个不可变的数组,所以String对象就是不可变的咯.
因此,String对象就是相当于一个常量啦,自然也就是不存在线程安全问题,换言之就是线程安全的.
然后,在性能方面,因为String对象是一个不可变的char型数组,所以每次改变String对象的时候都是会重新生成一个String对象,然后将新创建的String对象的地址赋给原来的变量.可想而知,如果存在大量的update操作时,使用String是多么的浪费资源.
**
**
通过查看继承树,我们发现,StringBuffer 和StringBuilder 都是继承于一个抽象类AbstractStringBuilder.
这里使用的就是设计模式中的建造者模式,AbstractStringBuilder作为抽象建造者,已经写好了各种方法,而StringBuilder和StringBuffer作为具体建造者去实现抽象建造者.(具体的设计模式,我以后会写再写,到时候贴过来~ )
下面以用的最多的append()方法举例:
我们都知道.StringBuffer和StringBuilder 都重载了很多append()方法,
查看源码能发现:
StringBuilder的append(String str)方法:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuffer的append(String str)方法:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
其实不难发现,StringBuilder和StringBuffer的方法其实在实现上没有很大区别,最大的就是StringBuffer在方法上加上了synchronized关键字.
然后我们看一下AbstractStringBuilder的属性和构造器
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage. //该值用于字符存储。
*/
char[] value; // 这个就是我们具体持有的char型数组
/**
* The count is the number of characters used. //用于记录 字符的个数
*/
int count;
/**
* 一个必须的空参构造器
*/
AbstractStringBuilder() {
}
/**
* Creates an AbstractStringBuilder of the specified capacity.
* 创建指定容量的AbstractStringBuilder
*/
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
下面我们来具体聊一聊这哥俩~
首先,我们来看看StringBuilder底层实现原理, 他持有的是他的"爸爸"AbstractStringBuilder的属性 char[ ] value,
而我们看一下StringBuilder的构造器:
/**
* StringBuilder的空参构造器
* 这就告诉我们,在使用空参构造器创建StringBuilder对象的时候,直接调用了父类的有参构造器,使我们持有的char型数组的长度变为了16.
*/
public StringBuilder() {
super(16);
}
/**
* 如果们使用有参构造器来初始化StringBuilder对象长度,那么我们就会将char型数组的初始化长度变为我们想要的长度(capacity)
*
* @param 初始化长度(大于0,小于0就会报错)
* @throws NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
public StringBuilder(int capacity) {
super(capacity);
}
/**
* 如果我们使用有参构造器在创建StringBuilder对象时传入String字符串的话,那么我们的char型数组的长度就变为了字符串长度+16
* @param str 初始化时传入的String字符串
*/
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
其实不止是StringBuilder如此,StringBuffer也是这样.
那么,我们不禁有一个疑问,因为这哥俩都是可变数组,那么当数组长度大于了我们的初始化长度时怎么办呢?
答案就是 调用父类的 newCapacity方法进行扩容
/**
* @minimumCapacity 就是最小容量
*
*/
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;
//这就是我们的扩容机制: 原数组长度 * 2 + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
由代码中我们可以看出,基本扩容就是 原数组长度 * 2 + 2.即 如果我们初始化长度是 16,那么第一次扩容之后的长度就变为了
16 * 2 + 2 = 34;
StringBuilder是线程不安全的.StringBuffer是线程安全的.这中的关键就是 synchronized 关键字.
StringBuffer中所有操作持有的char型数组的方法中都加入了synchronized关键字来保证线程安全,
同时StringBuffer有一个独有的
缓存属性, 用来保存最后进行操作的值,来提升一定的性能.但是总体性能还是要低于StringBuilder.
**
**
1.在只有少量字符串或者极少量字符串改变的情况下, 可以考虑使用String.
2.在单线程或者使用其他方式确保了数据线程安全情况下,优先考虑使用StringBuilder.
3.在多线程或者没有其他方式确保数据线程安全情况下,使用StringBuffer.