关于 String,StringBuffer 和 StringBuilder 的区别,相信每个初级 Java 工程师在前几次面试的时候都会被问到,当然,我也不例外。那么,我们应该怎样回答这个问题呢,又或者说他们三者在我们的技术基础结构中到底有多饱满?
一个普通的,经过培训或者自学不精的初级工程师可能是这样回答的:
String 创建的是一个不可变的字符串对象,如果对字符串进行修改那么就会创建一个新的String对象,并把当前引用指向这个对象; StringBuffer 和 StringBuilder 都是可变的字符串对象,其中引用指向的字符串对象内容的增加或者减少不会像 String 一样创建新的对象,只不过对 StringBuffer 的操作是线程安全的, StringBuilder 的操作是不安全的。
如上,这样的回答对么?对的,是正确的,但是看上去总感觉少点什么,感觉不够丰满。所以,这个时候我们就要开始祭出我们的大杀器“源码”来将这个问题深入,探讨一下造成以上结果的根本原因。
为什么 String 对象是不可变的呢,因为 String 类是被final修饰的。[斜眼笑]
public final class String //咳咳,兄弟,放下手中的菜刀,有话好好说
嘿嘿嘿,好了,我们重点来说一下StringBuffer和StringBuilder。
在JDK1.5版本之前,是没有 StringBuilder 的,而在1.0版本开始就有了StringBuffer。这时候的 StringBuffer 可以算是一心一意为了线程服务,几乎所有的方法都被* synchronized *关键字进行修饰,以append(String str)举例。
@Override //这是因为在JDK1.5版本之后,继承AbstractStringBuilder
public synchronized StringBuffer append(String str) {
toStringCache = null;//toStringCache被设置为null的话,JVM会对其先进行缓存,这个我们以后再说
super.append(str); //调用父类中的方法
return this;
}
在JDK1.5版本的时候,Sun 的工程师们可能是认为很多时候不需要 StringBuffer 参与到多线程任务中来,而在单线程中* synchronized 会影响虚拟机的执行效率,于是提出了在单线程中使用的,几乎所有方法都与 StringBuffer 相同的类 StringBuilder。然后把两者之间相同的地方抽象出来成为了 AbstractStringBuilder *。这时我们来看一下 StringBuilder 中的 append 方法
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
在 AbstractStringBuilder 中,append(String str) 这个方法是这样实现的:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);//扩容
str.getChars(0, len, value, count);//StringBuffer和StringBuilder底层是一个字符数组,这里是把str分解为字符数组再复制到StringBuffer或StringBuilder的字符数组中。
count += len;//count是这个对象的一个属性,代表着数组大小
return this; //返回的是当前对象
}
在上面只是对 StringBuffer 和 StringBuilder 线程安全性的一个对比,下面我们来说点别的,比如大小。
在初始化一个 StringBuffer 或者 StringBuilder 的时候,我们有三种方法,他们初始的大小都是多少呢? StringBuffer和StringBuilder 的构造规则一致,我们以StringBuffer举例。
1.默认(空参数)构造
public StringBuffer() {
super(16);
}
可以看出,StringBuffer 在空参初始化的时候以参数为“16”调用了父类的构造方法
AbstractStringBuilder(int capacity) {
value = new char[capacity];
//所以,StringBuffer创建了一个容量为“16”的字符数组。
}
所以说,有人问你无参构造 StringBuffer/StringBuilder 容量有多大的时候,会答了把~
2.有参(int capacity)构造
public StringBuffer(int capacity) {
super(capacity);
}
额,和第一点差不多,不进行赘述了。
3.有参(String str)构造
这种情况我们平时是用的最多的 StringBuffer sb = new StringBuffer(“xxxx”);
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
看到没,+16了看到没!设定容量为 str.length()+16 之后,调用了 append 方法, 将str转换为字符数组进行存储。
以上就是 StringBuffer/StringBuilder 初始构造的容量,那么我们下面来说说每次扩容增长容量和最大容量。
在 AbstractStringBuilder.java 也就是 StringBuffer 和 StringBuilder 所继承的父类中,有这样一段代码。
void expandCapacity(int minimumCapacity) { //扩容方法
//新容量 = 原来容量*2 + 2
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity; //因为Int存储机制的原因,在minimumCapacity超过一定范围之后,newCapacity 会为负数
if (newCapacity < 0) {
if (minimumCapacity < 0) // overflow 原容量范围过大,内存溢出
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;//最大容量
}
value = Arrays.copyOf(value, newCapacity);
}
所以说,StringBuffer/StringBuilder 每次扩容的时候,正常的大多数情况下新容量为老容量的2倍+2,最大容量为 Integer.MAX_VALUE。