Java语言中String、StringBuffer、StringBuilder这3个类可以用于对字符串进行操作,下面就来具体讨论一下3者的区别。
java.lang.String这个类表示一个不可变的字符串类,一旦String对象被创建它的值将不会被改变。而java.lang.StringBuffer和java.lang.StringBuilder都是可变的字符串类,当对象被创建之后仍然可以修改其值。因此,如果使用String来保存一个经常修改的字符串对象,在字符串被修改的过程中使用String要比使用StringBuffer与StringBuilder产生许多额外的附加操作,同时也会产生许多附加的无用的对象,这些无用的对象会被系统垃圾回收器进行回收从而影响程序的性能。
String与StringBuffer、StringBuilder另外一个区别是在创建对象时String对象可以通过构造函数(String s=
new String("Hello"))来创建,也可以通过赋值(String s="Hello")的方式来创建。而
StringBuffer和StringBuilder只能通过构造函数的方式来创建。
而StringBuffer与StringBuiler的区别主要在于StringBuffer是线程安全的,StringBuilder是线程不安全的,通过观察StringBuffer与StringBuilder对应的append方法源码可以发现有如下区别:
StringBuilder java.lang.StringBuilder.append(Object obj)源码:
/**
* Appends the string representation of the specified {@code Object}.
* The {@code Object} value is converted to a string according to the rule
* defined by {@link String#valueOf(Object)}.
*
* @param obj
* the {@code Object} to append.
* @return this builder.
* @see String#valueOf(Object)
*/
public StringBuilder append(Object obj) {
if (obj == null) {
appendNull();
} else {
append0(obj.toString());
}
return this;
}
/**
* Adds the string representation of the specified object to the end of this
* StringBuffer.
*
* If the specified object is {@code null} the string {@code "null"} is
* appended, otherwise the objects {@code toString} is used to get its
* string representation.
*
* @param obj
* the object to append (may be null).
* @return this StringBuffer.
* @see String#valueOf(Object)
*/
public synchronized StringBuffer append(Object obj) {
if (obj == null) {
appendNull();
} else {
append0(obj.toString());
}
return this;
}
通过以上源码可以发现,StringBuffer一些方法被synchronized同步关键字修饰,而StringBuidler一些方法却没有被关键字synchronized修饰,因此S
tringBuffer是线程安全的,而StringBuidler是线程不安全的,这也导致了
StringBuffer要比StringBuilder效率低。
因此从效率的角度来看,StringBuilder效率最高、StringBuffer效率次之,String效率最低。鉴于这种情况,一般情况下,如果操作的数据量比较小应优先考虑String类;如果是在单线程的程序中操作大量数据从效率的角度出发应该优先考虑StringBuilder;如果是在多线程的程序中操作大量数据从线程安全的角度出发应该优先考虑StringBuffer。
为了更好地体现String、StringBuffer、StringBuilder三者之间的效率差异,下面给出一个对String、StringBuffer、StringBuilder效率测试的例程。
public class StringBufferAndStringBuilderTest {
public static void testString()
{
String s="Hello";
String addString="World";
long startTime=System.currentTimeMillis();
for(int i=0;i<100000;i++){
s+=addString;
}
long endTime=System.currentTimeMillis();
long runTime=endTime-startTime;
System.out.println("testString,运行时间:"+runTime);
}
public static void testStringBuffer()
{
StringBuffer s=new StringBuffer("Hello");
String addString="World";
long startTime=System.currentTimeMillis();
for(int i=0;i<100000;i++){
s.append(addString);
}
long endTime=System.currentTimeMillis();
long runTime=endTime-startTime;
System.out.println("testStringBuffer,运行时间:"+runTime);
}
public static void testStringBuilder()
{
StringBuilder s=new StringBuilder("Hello");
String addString="World";
long startTime=System.currentTimeMillis();
for(int i=0;i<100000;i++){
s.append(addString);
}
long endTime=System.currentTimeMillis();
long runTime=endTime-startTime;
System.out.println("testStringBuilder,运行时间:"+runTime);
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
testString();
testStringBuffer();
testStringBuilder();
}
}
运行结果如下:
测试例程源码(github)
讨论完String、StringBuffer、StringBuilder三者之间的效率问题之后有一个疑问:StringBuffer与StringBuilder中append方法内部到底是如何实现的呢?话不多说,直接看源码。
这里先看java.lang.StringBuilder.append(char c)这个方法,源码如下:
/**
* Appends the string representation of the specified {@code char} value.
* The {@code char} value is converted to a string according to the rule
* defined by {@link String#valueOf(char)}.
*
* @param c
* the {@code char} value to append.
* @return this builder.
* @see String#valueOf(char)
*/
public StringBuilder append(char c) {
append0(c);
return this;
}
可以看到StringBuilder.append(char c)内部调用了一个名为append0的方法,查看append0源码内部实现如下:
final void append0(char ch) {
if (count == value.length) {
enlargeBuffer(count + 1);
}
value[count++] = ch;
}
该方法 java.lang.AbstractStringBuilder抽象类中的一个方法,而StringBuilder和StringBuffer均继承实现了该抽象方法,AbstractStringBuilder.append0(char ch)方法首先判断当前用于存放数据的容器数组容量count是否等于容器数组长度value.length,如果容器数组容量count等于容器数组长度value.length则java.lang.AbstractStringBuilder.enlargeBuffer(int min)调用对容器数组扩容。下面查看java.lang.AbstractStringBuilder.enlargeBuffer(int min)内部实现,其源码如下:
private void enlargeBuffer(int min) {
int newCount = ((value.length >> 1) + value.length) + 2;
char[] newData = new char[min > newCount ? min : newCount];
System.arraycopy(value, 0, newData, 0, count);
value = newData;
shared = false;
}
在 java.lang.AbstractStringBuilder.enlargeBuffer(int min)方法中首先创建了一个大于原数组value长度的新数组newData,然后调用java.lang.System.arraycopy将旧数组value中每个元素复制拷贝到新数组newData中,最后再让value数组指向newData。即StringBuiler.append方法内部实现过程如下:首先创建了一个比value数组更大的新数组,然后调用数组元素复制拷贝方法java.lang.System.arraycopy将value中的每一个元素复制拷贝到新数组中,最后让value指向这个新数组。
StringBuffer同样继承自java.lang.AbstractStringBuilder抽象类,StringBuffer.append方法内部实现过程与StringBuilder.append相同,具体细节可以查看相关源码在此就不再讨论。