Theme NexT works best with JavaScript enabled
0%

String,StringBuilder,StringBuffer

^ _ ^

Q & A

String,StringBuffer,StringBuilder的区别

  • 可变性:String 一旦创建不可变,如果修改即创建新的对象,StringBuffer 和 StringBuilder 可变,修改之后引用不变;
  • 拼接效率:String 对象直接拼接效率高,但是如果执行的是间接拼接,效率很低,而 StringBuffer 和 StringBuilder 的效率更高,同时 StringBuilder 的效率高于 StringBuffer;
  • 线程安全:StringBuffer 的方法是线程安全的,StringBuilder 是线程不安全的,在考虑线程安全的情况下,应该使用 StringBuffer。

编程测试

可变性测试

1
2
3
4
5
6
7
8
9
10
11
String s = "Hello";
String s1 = s;
StringBuilder strBuilder = new StringBuilder("Hello");
StringBuffer strBuffer = new StringBuffer("Hello");
s += " World";
strBuilder.append(" Word");
strBuffer.append(" Word");
System.out.println("s = " + s); \\s = Hello World
System.out.println("s1 = " + s1); \\s1 = Hello
System.out.println("strBuilder = " + strBuilder); \\strBuilder = Hello Word
System.out.println("strBuffer = " + strBuffer); \\strBuffer = Hello Word

s和s1的值不相等说明String是不可变的。因为如果s += xxx是为s加上后缀的话,那么指向s存储地址的s1应该也是会改变的。而事实是,s += xxx中,虚拟机先会实例一个StringBuilder,将s中的字符和新添加的xxx按顺序加入StringBuilder,然后调用StringBuilder的toString方法将其转换为一个新的String实例对象,并将s(引用地址)指向这个新的对象。

效率测试

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
final int N = 5000;
String s = new String();
StringBuilder builder = new StringBuilder();
StringBuffer buffer = new StringBuffer();
long startTime,endTime;
// String用时统计
startTime = System.currentTimeMillis();
for(int i = 0;i < N;i++) {
s += i;
}
endTime = System.currentTimeMillis();
System.out.printf("String%d次拼接操作耗时%d(ms)\n",N,endTime-startTime);
// StringBuilder用时统计
startTime = System.currentTimeMillis();
for(int i = 0;i < N;i++) {
builder.append(i);
}
endTime = System.currentTimeMillis();
System.out.printf("StringBuilder%d次拼接操作耗时%d(ms)\n",N,endTime-startTime);
// StringBuffer用时统计
startTime = System.currentTimeMillis();
for(int i = 0;i < N;i++) {
buffer.append(i);
}
endTime = System.currentTimeMillis();
System.out.printf("StringBuffer%d次拼接操作耗时%d(ms)\n",N,endTime-startTime);

结果:
String5000次拼接操作耗时51(ms)
StringBuilder5000次拼接操作耗时1(ms)
StringBuffer5000次拼接操作耗时2(ms)

线程安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
final int N = 500;
final int threadNum = 10;
//StringBuilder builder = new StringBuilder();
StringBuffer buffer = new StringBuffer();
for(int i = 0;i < threadNum;i++) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i < N;i++) {
//builder.append('a');
buffer.append('a');
}
}
}).start();
}
try {
Thread.sleep(1000);
//System.out.println("builder_length = " + builder.length());
System.out.println("buffer_length = " + buffer.length());
}catch(Exception e) {
e.printStackTrace();
}
  • 如果是用builder来运行的话,builder_length <= 5000 且可能出现ArrayIndexOutOfBoundsException异常。这是因为StringBuilder存在扩容机制,但append函数却没有用synchronized修饰,导致出现:多个线程访问StringBuilder的最后一个空闲名额时,都觉得不用扩容,但实际名额已经不够了;
  • 如果是用buffer来运行的话,builder_length == 5000 是恒定的,StringBuffer通过给其涉及修改的成员函数增加synchronized修饰符来保证操作的互斥性。