前言
在现实世界中,既有独居动物,也有群居动物。人作为一种高等动物,是一种群居动物。群居动物都离不开家族,String类上篇中已经详细分析了它的源码实现,几乎涵盖了大部分的源码。作为一个优秀的类,它是怎样产生的呢?它的家族、派系是怎样的?为了以后编码过程中更加清晰明白,不被混淆,这篇文章来对比分析一下String、StringBufffer、StringBuilder、AbstractStringBuilder 以及 CharSequence接口。
String的源码解析请看第四章:《第四章 JAVA的基础类之String源码分析(上)》
第四章 JAVA的基础类之String源码分析(上)
家族结构
上古传说:开天辟地有一虫,虫大成蛇,蛇大成蟒,蟒大成蚺,蚺大成蛟,蛟大成龙…成龙的儿子…扯远了。
String是怎么来的呢?
从图中可以看到,在1.8版本中,String 的父类有CharSequence 和 AbstractStringBuilder 。Sting的兄弟类有StringBuffer 和 StringBuilder。那么谁是老大、谁是老二、谁是老三呢?
接口 CharSequnce
这是一个字符序列的接口,它定义了一个字符串应该具备的行为特征。其中方法如下:
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
String toString();
AbstractStringBuilder
这是一个抽象类,诞生于 JAVA 1.5,和StringBuilder 一起诞生出来的。它是对StringBuffer 和StringBuilder
的抽象。
StringBuffer 和 StringBuilder
这两个类,也是final修饰过的,被final修饰过的类不能被继承,方法不能被重写。所以这个类提供的行为方法就是最基本的方法了。
(1)不同点
安全性比较
StringBuffer的所有方法,都是被synchronized修饰过的,因此它是线程安全的,但是速度也会受到影响而降低。
性能比较
StringBuilder的所有方法,没有被synchronized修饰,相比较StringBuffer,它是线程不安全的,但是单线程下,速度比StringBuffer快。其原因可能是StringBuffer的修饰词synchronized带来的消耗。
属性比较
它们都是继承的AbstractStringBuilder,其内部维护着一个数组 value 和 字符串长度 count;
count 与 value.length 它们是不同的。最开始的时候我也产生过这样的疑惑,那是在看ArrayList源码的时候,后来才反应过来。value.length 是指的容器的最大容量,count 是指的实际的容量。
实际容量 <= 最大容量;
但是StringBuffer内部单独维持着一个数组,StringBuilder没有。它是被transient修饰的属性,表示不会被序列化。
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
* 翻译: toString方法返回的最后一个值的缓存。在修改StringBuffer时清除
*/
private transient char[] toStringCache;
(2)相同点
构造器
两者的构造器使用无参构造函数时,其内部维持的数组长度默认为16.
如果使用有参构造时,其内部容量为16+参数的长度。
方法
其内部基本上都提供了相同的方法,方法都来自于父类,两者对其中的部分方法进行了重写,其区别就是StringBuffer加了synchronized修饰词,StringBuilder没有加。两者总结出来大概有【增】、【删】、【查】、【改】四大部分。并且其内部实现都是调用的父类的方法。
// 增: 主要是append方法和insert方法
append(CharSequence s)
append(CharSequence s, int start, int end)
append(char[] str)
append(char[] str, int offset, int len)
append(char c)
append(boolean b)
append(int i)
append(long lng)
append(float f)
append(double d)
insert(int index, char[] str, int offset,int len)
insert(int offset, Object obj)
insert(int offset, String str)
insert(int offset, char[] str)
insert(int dstOffset, CharSequence s)
insert(int dstOffset, CharSequence s,int start, int end)
// 删:
delete(int start, int end)
deleteCharAt(int index)
// 改:
replace(int start, int end, String str)
// 查:
indexOf(String str)
indexOf(String str, int fromIndex)
lastIndexOf(String str)
lastIndexOf(String str, int fromIndex)
toString()
这些方法的作用和String类中方法的作用区别不大,但是为什么已经有了String类这个优秀的儿子了,为什么还要诞生出它的兄弟StringBuffer 和 StringBuilder 呢?
StringBuffe诞生原因分析
从源码中可以得知,String诞生于JAVA 1.0,StringBuffer 也诞生于 JAVA 1.0。而 StringBuilder 诞生于 JAVA 1.5。
先来分析String 和 StringBuffer,这对双胞胎。
String 和 StringBuffer 都是final 修饰的,不可被继承。但是有一点不同,String 内部维护的数组也是被fina修饰的,而StringBuffer 内部维护的数组,没有被final修饰,是可变的。
String 类
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
private int hash; // Default to 0
private static final long serialVersionUID = -6849794470754667710L;
String 类虽然不是基本数据类型,但是它非常强大,内部有着丰富的API,使用程度非常高。但是这个类有一个缺点,由于其内部的数组是不可变的,所以String 类中涉及到的【增】、【删】、【改】操作时,凡是和char容量变更相关的操作,它只能重新创建一个String 类,意味着有一个新的容量的数组。这样的变更操作会产生大量的对象在堆中产生,非常影响性能!
StringBuffer 类
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence {
这个类继承了AbstractStringBuilder类,AbstractStringBuilder是JAVA 1.5才出现的,和StringBuilder 一致。
StringBuffer 中的属性被抽象到了 AbstractStringBuilder类中,AbstractStringBuilder属性中维护着一个数组char[ ] value:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
StringBuffer 继承了这个类,因此StringBuffer 中的这个数组是容量可变的。并且StringBuffer 中提供的API
大部分属于【增】、【删】、【改】。
因此,StringBuffer 很好的弥补了String类对于内部数组容量变更操作的缺点,它们相互协作可以提供功能方便、性能强大的API组合。具体是在哪些地方做了优化呢?下面通过StringBuffer的append方法来研究一下。
实例分析
// 下面左侧是行序
public static void main(String[] args) {
//1 long d ;
//2 long t = System.currentTimeMillis();
//3 StringBuffer sd = new StringBuffer();
//4 for (int i = 0; i < 10000; i++) {
//5 sd.append(i);
//6 }
//7 System.out.println((d=System.currentTimeMillis()) - t);
//8 String s = "";
//9 for (int i = 0; i < 10000; i++) {
//10 s += i;
//11 }
//12 System.out.println(System.currentTimeMillis() - d);
}
// 结果:
1
331
可以看到StringBuffer 在改变容量操作的过程中,性能远远高于String的自增。下面分析一下其内部原理。
(1) 构造器
在第3行创建StringBuffer 对象时,使用了无参构造,内部的char数组默认容量为16.
在第8行创建String类时,使用了无参构造,默认是空字符。
(2)扩容机制
String类的扩容机制就是重新new 一个String,创建一个新增需要的长度的char 数组。每一次循环,都会做这样的操作。也就是每一次都要new 新的String对象,并且底层复制新的数组出来。
StringBuffer 初始容量为16,
也就是char [ ] value = new char[16];
前16次循环只需要赋值到value中就可以了。超过了16呢?
StringBuffer 有这样的扩容机制:
在append() 方法中,其实现是调用的父类的方法。所以观察一下父类 AbstractStringBuilder:
// 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;
}
// ensureCapacityInternal 方法,这个是扩容
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
//newCapacity方法 , 计算新容量大小
private int newCapacity(int minCapacity) { // minCapacity 是所需容量
// overflow-conscious code
int newCapacity = (value.length << 1) + 2; // 设置新容量为原容量的 2倍+2
if (newCapacity - minCapacity < 0) { // 如果新容量仍然小于 所需的容量
newCapacity = minCapacity; // 则新容量为所需容量,否则新容量就为原来的 2倍 + 2
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
//hugeCapacity方法,针对计算容量过程中,计算出的容量不在 0 到 MAX_ARRAY_SIZE范围内时的解决方案:
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError(); // 如果 计算出的容量超过了Int 类型的最大范围即2^31,则报错
}
return (minCapacity > MAX_ARRAY_SIZE) // 否则就返回minCapacity 和 MAX_ARRAY_SIZE之间的最大值
? minCapacity : MAX_ARRAY_SIZE;
}
因此,正因为StringBuffer 有了这个扩容机制,所以在自增的时候,不需要新建一个对象,在性能上有了很大的提升。
这是String 和 StringBuffer 之间的区别以及StringBuffer 诞生的原因。
StringBuilder的诞生原因
StringBuilder 是 JAVA 1.5才出来的,相比于StringBuffer ,StringBuilder 在StringBuffer的基础上,提供的是线程不安全的API,减少了synchronized的使用,它们两者API都被抽象到了AbstractStringBuilder类中,其实现也是在父类中,所以两者除了这个区别以外,没有其他区别。
但是synchronized带来的区别有其二:
一是StringBuffer用synchronized修饰的方法是属于线程安全的,StringBuilder 是线程不安全的。
二是synchronized修饰的API速度比没有修饰的相同API更慢,其原因暂不清楚,但是可以推测应该是synchronized带来的性能消耗。具体待以后演技JVM时,再来做确定的答案。
总结:
回到开头的问题,String和StringBuffer 是双胞胎,StringBuilder 是老三。