字符串拼接之String、StringBuilder、StringBuffer
在写Java代码的过程中,我们经常会用到字符串的拼接,但是你知道他们的区别?废话不多说,直接上测试代码。
一. 效率比较
1. String拼接字符串的代码
public static String testString(int appendTimes) {
String str = "";
if (appendTimes < 0) {
return str;
}
long begin = ();
while (appendTimes-- > 0) {
str += "s";
}
("String time: " + (() - begin));
return str;
}
2. StringBuilder拼接字符串的代码
public static String testStringBuilder(int appendTimes) {
StringBuilder buffer = new StringBuilder("");
if (appendTimes < 0) {
();
}
long begin = ();
while (appendTimes-- > 0) {
("s");
}
System.out.println("StringBuilder time: " + (() - begin));
return ();
}
3. StringBuffer拼接字符串的代码
public static String testStringBuffer(int appendTimes) {
StringBuffer buffer = new StringBuffer("");
if (appendTimes < 0) {
();
}
long begin = ();
while (appendTimes-- > 0) {
("s");
}
System.out.println("StringBuffer time: " + (() - begin));
return ();
}
4. 测试代码
public static void main(String[] args) {
int times = 1000;
(times);
(times);
(times);
}
5. 测试结果
我们分别用三种拼接方式进行10次、10000次、1000000次字符拼接,结果如下(时间单位:ms):
//拼接10次结果
String time: 0
StringBuilder time: 0
StringBuffer time: 0
//拼接10000次结果
String time: 109
StringBuilder time: 1
StringBuffer time: 1
//拼接1000000次结果
String time: 223065
StringBuilder time: 14
StringBuffer time: 20
从测试结果上看,进行10次拼接时:三种方式用时都很少;进行10000次拼接时:String耗时远远超过了StringBuilder 和StringBuffer;进行1000000次拼接时:String性能令人担忧,用了近4分钟,StringBuilder 和StringBuffer却相差无几;
进而可以得出这样的结论:在进行高频率的字符串拼接时,三种方式的效率顺序为:StringBuilder > StringBuffer > String。
二. 原因剖析
1. String
String类在JDK中的源码如下:
public final class String
implements , Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[];
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
........
}
从源码中可以看出,String是一个final类,并且是用一个final修饰的char型数组来存放字符串的。Java中一个变量被final修饰,初始化赋值后,再次赋值就编译器就会报错,因此这里需要记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。(有困惑的小伙伴可以看一下:深入理解Java中的String)对于以下简单代码:
String s1 = "s";
s1 += "abc";
编译器实则是new了一个新的String对象,赋值为“sabc”,然后修改s1的引用。原来的对象则需要被GC(垃圾回收器回收)。
2. StringBuilder和StringBuffer
查看JDK StringBuilder和StringBuffer两个类源码:
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
......
}
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
......
}
从上面可以看出:StringBuilder和StringBuffer继承了同一个父类AbstractStringBuilder,我们在查看AbstractStringBuilder JDK中的源码:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
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;
}
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > ) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
}
根据源码可知:AbstractStringBuilder用一个char型value[]存放字符内容,其append方法实际上的做个两关键操作,1)对value[]进行扩容;2)将新增字符串复制到value[]尾部,并不是像String那样新建一个对象,String效率低下的主要原因是在不停地new对象。
StringBuilder和StringBuffer性能上的差距是在于StringBuffer 大部分方法(append也不例外)都加了synchronized关键字,同步是需要消耗一些计算资源的,因此StringBuffer相对于StringBuilder会稍微慢一些,但是StringBuffer中这些加了synchronized的方法是线程安全的,有兴趣的朋友可以看一下JDK源码。
参考文章:
- 深入理解Java中的String