Difference between StringBuilder and StringBuffer
技术背景
在 Java 编程中,处理字符串是常见的操作。String
类是不可变的,当需要频繁修改字符串内容时,使用 String
会导致性能问题,因为每次修改都会创建一个新的 String
对象。为了解决这个问题,Java 提供了 StringBuffer
和 StringBuilder
两个类,它们都是可变的字符串序列。
实现步骤
相似点
StringBuilder
和 StringBuffer
都是可变的,意味着可以在同一位置更改它们的内容。并且它们都有相同签名的方法。
不同点
- 线程安全性:
StringBuffer
是同步的,因此是线程安全的;而 StringBuilder
是非同步的,不是线程安全的。 - 性能:由于
StringBuffer
的同步机制会带来额外的开销,所以 StringBuilder
的性能通常比 StringBuffer
更好。
代码示例
以下是一个简单的基准测试代码,用于比较 StringBuffer
和 StringBuilder
的性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class Main { public static void main(String[] args) { int N = 77777777; long t;
{ StringBuffer sb = new StringBuffer(); t = System.currentTimeMillis(); for (int i = N; i > 0 ; i--) { sb.append(""); } System.out.println(System.currentTimeMillis() - t); }
{ StringBuilder sb = new StringBuilder(); t = System.currentTimeMillis(); for (int i = N; i > 0 ; i--) { sb.append(""); } System.out.println(System.currentTimeMillis() - t); } } }
|
运行这个测试,StringBuffer
可能需要 2241 ms,而 StringBuilder
只需要 753 ms。
以下是一个多线程环境下的测试代码:
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;
public class StringsPerf {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); StringBuffer buffer = new StringBuffer(); for (int i = 0 ; i < 10; i++){ executorService.execute(new AppendableRunnable(buffer)); } shutdownAndAwaitTermination(executorService); System.out.println(" Thread Buffer : " + AppendableRunnable.time);
AppendableRunnable.time = 0; executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); StringBuilder builder = new StringBuilder(); for (int i = 0 ; i < 10; i++){ executorService.execute(new AppendableRunnable(builder)); } shutdownAndAwaitTermination(executorService); System.out.println(" Thread Builder: " + AppendableRunnable.time);
}
static void shutdownAndAwaitTermination(ExecutorService pool) { pool.shutdown(); try { if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); if (!pool.awaitTermination(60, TimeUnit.SECONDS)) System.err.println("Pool did not terminate"); } } catch (Exception e) {} } }
class AppendableRunnable<T extends Appendable> implements Runnable {
static long time = 0; T appendable; public AppendableRunnable(T appendable){ this.appendable = appendable; }
@Override public void run(){ long t0 = System.currentTimeMillis(); for (int j = 0 ; j < 10000 ; j++){ try { appendable.append("some string"); } catch (Exception e) {} } time+=(System.currentTimeMillis() - t0); } }
|
在多线程环境下,StringBuffer
可以正常工作,但 StringBuilder
可能会抛出 java.lang.ArrayIndexOutOfBoundsException
异常。
最佳实践
- 当需要在单线程环境下频繁修改字符串内容时,建议使用
StringBuilder
,因为它的性能更好。 - 当需要在多线程环境下频繁修改字符串内容时,建议使用
StringBuffer
,以确保线程安全。
常见问题
1. StringBuilder
和 StringBuffer
的性能差异在所有情况下都很大吗?
在单线程环境下,StringBuilder
通常比 StringBuffer
快,因为它不需要同步机制带来的额外开销。但在某些小迭代的情况下,由于 JVM 优化(如锁消除),性能差异可能可以忽略不计。
2. StringBuilder
一定不能在多线程环境中使用吗?
StringBuilder
不是线程安全的,但如果能确保在多线程环境中对 StringBuilder
的访问是串行的,或者通过其他方式进行同步,也可以使用。不过,在这种情况下,使用 StringBuffer
会更简单和安全。