StringBuilder vs StringBuffer:如何选择以及为什么这很重要?
\n\n如果你曾经编写过大量处理字符串的Java代码(例如,拼接SQL查询、生成JSON或只是将几十个文本片段连接起来),那么你很可能遇到过这个两难选择:StringBuilder还是StringBuffer?在Stack Overflow上,这个问题已经积累了数十万次浏览,这并非没有原因。乍一看,这两个类就像双胞胎一样相似——它们都继承自AbstractStringBuilder,都使用可变的字符数组,都支持append()、insert()、delete()方法。但正如常言所说,魔鬼藏在细节中。
在本文中,我们不仅仅会列举它们的区别。我们将通过实际示例进行深入分析,探究JVM的内部机制,并回答你在面试中一定会被问到的问题。让我们开始吧!
\n\n1. 主要区别:同步
\n\n这是最重要的一点,也是所有问题的根源。StringBuffer是线程安全的。它几乎所有公共方法都使用synchronized关键字进行了同步。StringBuilder则没有同步。仅此而已。
让我们看一下代码(来自真实JDK的简化版本):
\n\n// StringBuffer (Java 17)\npublic synchronized StringBuffer append(String str) {\n toStringCache = null;\n super.append(str);\n return this;\n}\n\n// StringBuilder\npublic StringBuilder append(String str) {\n super.append(str);\n return this;\n}\n\n\n一行synchronized——这就是全部区别。但这行代码改变了一切:性能、应用场景,甚至应用程序的架构。
何时使用StringBuffer?
\n- \n
- 你在编写多线程应用程序。 \n
- 同一个
StringBuffer实例在没有外部同步的情况下被多个线程使用。 \n - 示例:一个日志记录器,它从不同线程收集消息到一个缓冲区中。 \n
何时使用StringBuilder?
\n- \n
- 单线程代码(90%的情况)。 \n
- 方法内部的局部变量。 \n
- 字符串拼接的循环(编译器本身会建议使用StringBuilder)。 \n
2. 性能:谁更快?
\n\n这一点是显而易见的:StringBuilder比StringBuffer更快。快多少?取决于负载,但平均快2-5倍。让我们通过一个简单的基准测试来验证。
\n\npublic class PerformanceTest {\n private static final int ITERATIONS = 10_000_000;\n\n public static void main(String[] args) {\n // StringBuilder\n long start = System.nanoTime();\n StringBuilder sb = new StringBuilder();\n for (int i = 0; i < ITERATIONS; i++) {\n sb.append("a");\n }\n long sbTime = System.nanoTime() - start;\n\n // StringBuffer\n start = System.nanoTime();\n StringBuffer sbf = new StringBuffer();\n for (int i = 0; i < ITERATIONS; i++) {\n sbf.append("a");\n }\n long sbfTime = System.nanoTime() - start;\n\n System.out.println("StringBuilder: " + sbTime / 1_000_000 + " ms");\n System.out.println("StringBuffer: " + sbfTime / 1_000_000 + " ms");\n System.out.println("Buffer慢 " + (sbfTime / (double) sbTime) + " 倍");\n }\n}\n\n\n在我的笔记本电脑上(Intel i7, 16GB RAM, Java 17),结果很稳定:StringBuilder ~180 ms,StringBuffer ~620 ms。差距接近3.5倍。如果加上实际工作(解析、格式化),差距可能会更大。为什么?因为每次调用synchronized都涉及监视器检查、锁获取和释放。在单线程代码中,这纯粹是浪费时间。