对 S t r i n g , S t r i n g B u f f e r , S t r i n g B u i l d e r String,StringBuffer,StringBuilder String,StringBuffer,StringBuilder三者各做6000次字符串拼接,观察其分别使用的内存和时间。
/**
* 对String,StringBuilder,StringBuffer进行字符串拼接性能比较
*/
public class String_StringBuilder_StringBuffer {
public static void main(String[] args) {
int nums = 6000;//进行字符串拼接的次数
//使用String进行字符串的拼接
String str = "";
long memoryStart1 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
long timeStart1 = System.currentTimeMillis();//获取系统的当前时间
for (int i = 0; i < nums; i++) {
str = str + i;
}
long memoryEnd1 = Runtime.getRuntime().freeMemory();
long timeEnd1 = System.currentTimeMillis();
System.out.println("String占用内存:" + (memoryEnd1 - memoryStart1));
System.out.println("String占用时间:" + (timeEnd1 - timeStart1));
//使用StringBuilder进行字符串的拼接
StringBuilder builder = new StringBuilder("");
long memoryStart2 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
long timeStart2 = System.currentTimeMillis();//获取系统的当前时间
for (int i = 0; i < nums; i++) {
builder = builder.append(i);
}
long memoryEnd2 = Runtime.getRuntime().freeMemory();
long timeEnd2 = System.currentTimeMillis();
System.out.println("StringBuilder占用内存:" + (memoryEnd2 - memoryStart2));
System.out.println("StringBuilder占用时间:" + (timeEnd2 - timeStart2));
//使用StringBuffer进行字符串的拼接
StringBuffer buffer = new StringBuffer("");
long memoryStart3 = Runtime.getRuntime().freeMemory();//获取系统剩余内存
long timeStart3 = System.currentTimeMillis();//获取系统的当前时间
for (int i = 0; i < nums; i++) {
buffer = buffer.append(i);
}
long memoryEnd3 = Runtime.getRuntime().freeMemory();
long timeEnd3 = System.currentTimeMillis();
System.out.println("StringBuffer占用内存:" + (memoryEnd3 - memoryStart3));
System.out.println("StringBuffer占用时间:" + (timeEnd3 - timeStart3));
}
}
输出结果:
String占用内存:188816272
String占用时间:108
StringBuilder占用内存:0
StringBuilder占用时间:0
StringBuffer占用内存:0
StringBuffer占用时间:1
从结果来看, S t r i n g String String所用的时间和占用的内存远远高于 S t r i n g B u i l d e r StringBuilder StringBuilder和 S t r i n g B u f f e r StringBuffer StringBuffer,下面就来分析一下原因。
在Java中,字符串属于对象,String类的字符串对象初始化后是不能够改变其中的字符的,因此每次对String类的字符串的每一次改动,都会生成新的String对象(开辟新的堆内存),所以导致效率低下,且浪费内存空间。
String字符串对象进行拼接操作的内存变化如下:
打开String类的源码看看
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
public String() {
this.value = "".value;
}
String 类内部使用char数据来保存字符串的value,并且使用关键字final修饰。
而final实例字段必须在构造对象时初始化,并且之后不能再修改这个字段,所以String类的字符串对象是不可变的。
每次修改字符串都需要重新创建一个新的字符串对象,那一定会降低运行效率吗?也不尽然。不可变的String类虽然不像StringBuilder和StringBuffer那样,可以直接修改代码单元,而不用创建新的对象,但String是不可变的字符串,同样有着一个很大的优点:编译器可以让字符串共享。
字符串对象在JVM中可能有两个存放的位置:字符串常量池或堆内存。
对于存储在公共的字符串常量池中的字符串,字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串和复制的字符串共享相同的字符。Java的设计者认为共享带来的高效率远远超过拼接字符串所带来的低效率,同时大多数情况下,我们只需要对字符串进行比较等操作,并不需要改变字符串。因此,String算是有利有弊吧。
和 String 类不同的,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不会产生新的对象。我们先看StringBuilder的源码来分析。
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
从StringBuilder的append()方法看不出来什么,注意到StringBuilder是继承了AbstractStringBuilder
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
那么继续查看AbstractStringBuilder的源码
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;
AbstractStringBuilder同样采用char数组来保存值,但是并没有用final修饰,说明AbstractStringBuilder对象是可变的。
再看append()方法
/*
* @param str a string.
* @return a reference to this object.
*/
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;
}
在append()方法里调用了getChars()方法,继续查看getChars()方法
getChars()方法是String类中的方法
/*
* @param srcBegin index of the first character in the string to copy.
* @param srcEnd index after the last character in the string to copy.
* @param dst the destination array.
* @param dstBegin the start offset in the destination array.
*/
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);
}
getChars()方法继续调用System提供了一个静态方法arraycopy(),将追加append的值复制到原字符串(char数组)末尾。所以StringBuilder就是通过这样对数组的扩容、复制操作完成字符串拼接(append)。
附系统方法arraycopy()的定义:
/*
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
讲完了StringBuilder,我们再来看StringBuffer
StringBuffer和StringBuilder类功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的
先上源码:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
可见 StringBuffer和StringBuilder一样,同样继承自AbstractStringBuilder。
再来看append()方法
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
和StringBuilder一样,都是调用父类AbstractStringBuilder的方法来实现,但不同的是,StringBuffer通过Synchronized给方法加了锁,是线程安全的。