简析String、StringBuffer与StringBuilder

字符串类String

String是final类。Java程序中的所有字符串字面值(如”abc”)都作为此类的实例实现。字符串是常量,它们的值在创建之后不能更改,如果对已经存在的String对象进行修改,都是重新new一个对象,然后把修改后的值保存进去。字符串缓存区支持可变的字符串。因为String对象是不可变的,所以可以共享。
字符串的定义很简单,直接给一个字符串类型的变量赋值即可,例如:

1
String Str = "abc";

等价于:

1
2
char data[] = {'a','b','c'}
String str = new String(data);

线程安全的可变字符串类StringBuffer

StringBuffer 是一个线程安全的可变字符串类,通过构造方法创建对象。类似于String的字符串,不同的是它通过某些方法调用改变该序列的长度和内容。它可将字符串缓存区安全地用于多个线程,可以在必要时对这些方法进行同步。

1
2
StringBuffer();                                //构造一个空字符串的字符串缓存区
StringBuffer(String str); //构造一个字符串缓存区,并将其内容初始化为指定的字符串内容

StringBuffer上的主要操作是append()和insert()方法。通过查看StringBuffer类的三段源码,我们会发现最后调用了System.arraycopy来进行来修改字符串。

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

1
2
3
4
5
6
7
8
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
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);
}

可变字符串类StringBuilder

此类提供一个与StringBuffer兼容的API,但不保证同步。该类被设计用作StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,它比StringBuffer要快。

如何选择

  • String是字符串常量
  • StringBuffer是字符串变量(线程安全)
  • StringBuilder是字符串变量(非线程安全)

简单地说,String类型和StringBuffer类型的主要性能区别在于: String是不可改变的对象,每次对String类型进行改变的时候,其实都等同于生成了一个新的String对象,然后将引用指向该对象;而对于StringBuffer类,每次操作都是对StringBuffer对象本身进行更改。
所以,如果经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后,JVM的GC就会开始工作,那速度是一定会相当慢的。这种情况推荐使用StringBuffer,特别是字符串对象经常改变的情况下。但是某些特别情况下,String对象的字符串拼接其实是被JVM解释成了StringBuffer对象的拼接,所以这个时候String对象的速度并不会比StringBuffer对象慢,而特别是以下的字符串对象生成中,String效率是远要比StringBuffer快的:

1
2
String str = "This is only a " + "simple" + "test";
StringBuffer builder = new StringBuilder("This is only a ").append("simple").append("test");

你会发现,生成str对象的速度明显快多了。其实这是JVM的一个隐藏的实现机制,实际上:

1
String str = "This is only a " + "simple" + "test";

其实就是:

1
String str = "This is only a simple test";

但是要注意,如果你的字符串是来自另外的String对象,速度就没那么快了,譬如:

1
2
3
4
String str1 = "This is only a ";
String str2 = "simple";
String str3 = "test";
String str4 = str1+str2+str3;

为了测试这3种类型当累加不同次数字符串时的效率,我们编写一个测试类,分别按次数累加字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class TestString {

static int count = 100; //循环次数

//测试String
public static void testString() {
long start = System.nanoTime();
String str = "";
for (int i = 0; i < count; i++) {
str += "," + i;
}
long end = System.nanoTime();
System.out.println("String: " + (end - start));
}

//测试StringBuffer
public static void testStringBuffer() {
long start = System.nanoTime();
StringBuffer str = new StringBuffer();
for (int i = 0; i < count; i++) {
str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuffer: " + (end - start));
}

//测试StringBuilder
public static void testStringBuilder() {
long start = System.nanoTime();
StringBuilder str = new StringBuilder();
for (int i = 0; i < count; i++) {
str = str.append(",").append(i);
}
long end = System.nanoTime();
System.out.println("StringBuilder: " + (end - start));
}

public static void main(String[] args) {
TestString.testString();
TestString.testStringBuffer();
TestString.testStringBuilder();
}

}

运行该程序执行的测试时间如表所示:

毫微秒 String StringBuffer StringBuilder
1次 38292 32765 1579
10次 52108 45792 6711
100次 230143 70662 64345
1000次 11072907 308305 181193
1万次 400656874 892543 814777
10万次 溢出 4308762 4372318
100万次 溢出 100687270 49812689

String在10万次循环时就溢出了,而StringBuffer在100万次循环时间为100ms,StringBuilder的时间为49ms。显然选择优先级为: StringBuilder > StringBuffer > String 。因此,对于这3个类的使用,我们需要按照以下情况去选择。

  • 如果偶尔对简单的字符串常量进行拼接,那么可以使用String,它足够简单而且轻量级。
  • 如果需要经常进行字符串的拼接、累加操作,请使用StringBuffer或者StringBuilder。
  • 如果是在单线程的环境中,建议使用StringBuilder,它要比StringBuffer快;如果是在多线程的环境中,建议使用StringBuffer,它是线程安全的。
    因此,StringBuilder实际上是我们的首选,只有在多线程时才可以考虑使用StringBuffer,只有在字符串的拼接足够简单时才使用String。
0%