基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java 字符串: String, StringBuffer, StringBuilder 详解

知识点图片

本文对比了String、StringBuffer和StringBuilder:String不可变;后两者可变,核心区别在于线程安全与性能。

这是一个在Java面试和日常开发中都非常重要的问题。我们来详细地解析一下String, StringBuffer, 和 StringBuilder 的区别。

一句话总结

  • String不可变的字符序列。
  • StringBuffer可变的字符序列,线程安全,但效率较低。
  • StringBuilder可变的字符序列,线程不安全,但效率最高。

核心区别对比

特性 String StringBuffer StringBuilder
可变性 (Mutability) 不可变 (Immutable) 可变 (Mutable) 可变 (Mutable)
线程安全性 (Thread Safety) 线程安全 线程安全 (Synchronized) 线程不安全
性能 (Performance) 在频繁修改时性能最低 在频繁修改时性能中等 在频繁修改时性能最高
底层实现 private final char[] value char[] value char[] value
适用场景 少量、基本不改变的字符串 多线程环境下共享、频繁修改的字符串 单线程环境下、频繁修改的字符串

深入解析

1. String (不可变性)

String 对象一旦被创建,其内容就不能被修改。它的所有看似修改的操作(如 +concatsubstring 等)实际上都是创建了一个新的 String 对象,而原始对象保持不变。

为什么不可变?
查看String的源码,你会发现它的核心是一个被 final 修饰的字符数组:

java
private final char[] value;

final 关键字意味着 value 数组的引用地址不可改变,并且Java的实现保证了数组内容也不会被外部方法修改。

不可变性的优缺点:

  • 优点
    • 线程安全:因为无法修改,所以可以在多线程环境中安全共享,无需任何同步操作。
    • 可用于哈希键String的哈希码(hashCode)在创建时被计算并缓存,因为内容不变,哈希码也不会变,这使得它非常适合用作 HashMap 的键。
    • 字符串常量池 (String Pool):Java可以存储和复用相同的字符串字面量,节省内存。
  • 缺点
    • 性能开销:每次修改都会创建新对象,如果在一个循环中进行大量字符串拼接,会产生大量垃圾对象,严重影响性能并给GC(垃圾回收)带来巨大压力。

示例:

java
String str = "Hello";
str = str + " World"; // 这行代码创建了一个新的String对象,"Hello World",然后让str引用指向它。
                      // 原来的"Hello"对象仍然存在于内存中,等待被垃圾回收。

2. StringBuffer (线程安全的可变字符串)

StringBuffer 是为了解决 String 不可变性带来的性能问题而设计的。它是一个可变的字符序列。

如何实现可变性?
它的底层也是一个字符数组,但这个数组不是 final,并且有预留的容量。当你使用 append()insert() 等方法修改字符串时,它会直接在原有的字符数组上进行操作。如果数组容量不足,它会自动扩容。

如何保证线程安全?
StringBuffer 的几乎所有公开方法(如 append, insert, delete)都使用了 synchronized 关键字进行修饰。

java
// StringBuffer.java 的部分源码
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

这意味着在同一时间,只有一个线程能调用 StringBuffer 实例的这些方法。

  • 优点:线程安全,可以在多线程环境中安全使用。
  • 缺点:由于synchronized带来的同步锁开销,其性能相较于 StringBuilder 会差一些。

3. StringBuilder (非线程安全的可变字符串)

StringBuilder 是在 Java 5 中引入的,它在API上与 StringBuffer 完全兼容,但去掉了线程安全保障。

为什么更快?
它的方法没有 synchronized 关键字修饰。在单线程环境下,它省去了加锁和解锁的开销,因此执行效率更高。

示例:

java
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 直接在内部的char[]数组上修改,不会创建新对象
System.out.println(sb.toString()); // 输出 "Hello World"

性能对比演示

来看一个在循环中拼接字符串的经典例子,直观感受性能差异。

java
public class PerformanceTest {
    public static void main(String[] args) {
        int iterations = 50000;

        // 1. 使用 String
        long startTime1 = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < iterations; i++) {
            str += "item"; // 每次循环都会创建一个新的String对象
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println("String took: " + (endTime1 - startTime1) + " ms");

        // 2. 使用 StringBuffer
        long startTime2 = System.currentTimeMillis();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < iterations; i++) {
            buffer.append("item");
        }
        long endTime2 = System.currentTimeMillis();
        System.out.println("StringBuffer took: " + (endTime2 - startTime2) + " ms");

        // 3. 使用 StringBuilder
        long startTime3 = System.currentTimeMillis();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < iterations; i++) {
            builder.append("item");
        }
        long endTime3 = System.currentTimeMillis();
        System.out.println("StringBuilder took: " + (endTime3 - startTime3) + " ms");
    }
}

典型输出结果:

plaintext
String took: 2650 ms
StringBuffer took: 3 ms
StringBuilder took: 1 ms

(具体时间取决于机器性能,但数量级的差异是显而易见的)

结果清晰地表明:StringBuilder > StringBuffer > String


使用场景总结 (如何选择?)

  1. 使用 String

    • 当字符串内容基本不发生改变时。
    • 例如:常量、配置信息、短小的临时变量。
  2. 使用 StringBuilder

    • 首选!单线程环境下,需要对字符串进行频繁的添加、删除等操作时。
    • 例如:在循环中拼接字符串、组装JSON/XML、构建复杂的SQL查询语句。
  3. 使用 StringBuffer

    • 多线程环境下,当多个线程需要共享并操作同一个字符串对象时。
    • 例如:一个被多个线程共享的日志记录器或计数器。
    • 注意:这是一种相对少见的需求。在现代并发编程中,通常有更好的方式来处理共享状态(如使用 ConcurrentLinkedQueue 然后统一处理,或者使用 ThreadLocal),所以 StringBuffer 的使用频率已经大大降低。

简单来说,遵循以下原则:

  • 少量修改 -> String
  • 单线程大量修改 -> StringBuilder
  • 多线程大量修改 -> StringBuffer (谨慎使用)
00:00
00:00