源码解析String,StringBuffer,StringBuilder的区别

仅供学习交流,如有错误请指出,如要转载请加上出处,谢谢

String,StringBuffer,StringBuilder三者是处理字符串的常用类,String是在JDK1.0时就存在的字符串处理类,也是使用最广泛的,StringBuffer也是JDK1.0开始发布的线程安全的字符串处理类,他改变了原有String不可改变的字符串,增加了一个缓冲的概念,StringBuilder是JDK1.5提出的字符串处理类,他取消了StringBuffer原有的线程安全的特性,增加了字符串处理的效率。

其实要说哪种处理字符串是性能最好的,我觉得是哪种场景上的字符串处理性能是最好的,因为他们是各有千秋,下面是基于JDK1.8的源码解析这三种字符串处理方式的区别

String

1
2
3
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];

由上可知,String实现了序列化和字符序列两个接口,这会带来两个特性,一个是可传输的特性,一个是字符处理的特性,再看String的核心属性valuevalue是存储字符串的载体,由于被final修饰了,所有该字符串载体一旦初始化了就是不可变的,正是String不可变这一特性,所以它在处理字符串的方式只能通过构造初始化的方式来实现

1.初始化一个空串

1
2
3
public String() {
this.value = "".value;
}

2.初始化一个字符串对象

1
2
3
4
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}

3.初始化一个字符串

1
2
3
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

4.初始化字节类型的字符串

1
2
3
public String(byte ascii[], int hibyte) {
this(ascii, hibyte, 0, ascii.length);
}

当然还可以初始化StringBuffer,StringBuilder来构建,String的构造方法很丰富,提供了很多类型且多样的数据源来支持,如果你想要构建一个不变的或者变化不多的字符串,String很适合你

StringBuffer

1
2
3
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{

在StringBuffer继承体系来看,StringBuffer拥有String应有的特性,而且还继承了AbstractStringBuilder这个抽象类,这个类定义了StringBuffer的存储载体(也是StringBuilder的存储载体),现在通过一个append方法来看看StringBuffer处理字符串的方式

1
2
3
4
5
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

由上可知,这是一个StringBuffer拼接字符串的方法,由synchronized来修饰,所以StringBuffer是线程安全的(其实StringBuffer几乎所有的方法都是由synchronized修饰),这里它是调用的父类的append方法

1
2
3
4
5
6
7
8
9
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;
}

这是父类AbstractStringBuilder的append方法,在方法内有是两个很重要方法,也就是缓冲区的实现

  • 扩容方法:ensureCapacityInternal(int minimumCapacity)
  • 赋值方法:str.getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)

我们先看看扩容方法

1
2
3
4
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0)
expandCapacity(minimumCapacity);
}

这里会对所拼接的字符串做一个判断,也就是所拼接的字符串不能为空

1
2
3
4
5
6
7
8
9
10
11
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0)
newCapacity = minimumCapacity;
if (newCapacity < 0) {
if (minimumCapacity < 0)
throw new OutOfMemoryError();
newCapacity = Integer.MAX_VALUE;
}
value = Arrays.copyOf(value, newCapacity);
}

以上代码可知,他会先对原有的字符数组的容量扩大两倍再加二,在和最小的容量(原有的容量+拼接字符串的容量)比较,没有最小容量大就取最小容量,这里也有一个以防内存溢出导致容量为负数的一个处理,最后核心的就是 Arrays.copyOf(value, newCapacity),Arrays这是一个数组的工具类,copyOf方法是针对数组在原有的基础上改变其容量,这就是等于对原有的字符串数组进行了扩容,在回来看看将拼接的字符数组重新添加到新的字符数组的方法

1
2
3
4
5
6
7
8
9
10
11
12
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}

该方法开始会对不符合条件进行异常处理,最后的调用System.arraycopy方法将拼接字符数组添加到新的字符数组,这就完成了一个字符串拼接的过程,也就是缓冲池的实现,在多线程的情况下
保证安全的前提字符串大量的变动,使用StringBuffer是最好的

StringBuilder

1
2
3
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

其实从父类体系来看,StringBuilder和StringBuffer是一样的,那StringBuilder与StringBuffer有什么区别呢?让我们看看它的append的方法

1
2
3
4
public StringBuilder append(String str) {
super.append(str);
return this;
}

这里你就会方法它也是调用父类的append方法,而他们的父类又是同一个,这里不同的就是StringBuilder的append方法没有用synchronized修饰,所以它是不安全的,当然也就意味着在单线程的情况下比StringBuffer的性能是要好的,其实对于StringBuffer和StringBuilder来讲,它们都是对于AbstractStringBuilder类的实现,或许会有其他细节处理的不同,大致来讲,他们的区别就是一个是线程安全的实现,一个是线程不安全的实现,只是要你自己去权衡线程安全与性能之间的抉择

总结

StringBuilder或者StringBuffer并不一定比String的效率高,在各个场景中有不同的用法而已,对于StringBuilder和StringBuffer来讲,也不是施了什么魔法,只是实现了CopyOnWrite的思想,对字符串的操作的性能更高了,应对不同情况的选择而已