【JavaSE】String,StringBuider,StringBuffer

拼接 SQL 语句怎么做

在 Java 中,如果你要拼接 SQL 语句(比如动态生成查询或插入语句),选择使用 StringStringBuilder 还是 StringBuffer,取决于你的具体场景。下面是三者的对比和推荐使用方式。


它们的本质区别:

类名线程安全可变性性能
String✅ 不可变❌ 不可变❌ 多次拼接性能差
StringBuilder❌ 非线程安全✅ 可变✅ 推荐用于单线程
StringBuffer✅ 线程安全✅ 可变⚠️ 多线程下可用,性能略低于 StringBuilder

用哪个?

情况一:单线程环境(如 Web 应用中的 Controller、Service)

推荐使用:StringBuilder

  • 大多数业务场景中都是单线程处理请求。
  • StringBuilder 性能最好,没有同步开销。
StringBuilder sql = new StringBuilder("SELECT * FROM users WHERE 1=1");

if (name != null) {
    sql.append(" AND name = '").append(name).append("'");
}
if (age > 0) {
    sql.append(" AND age = ").append(age);
}

System.out.println(sql.toString());

情况二:多线程环境下共享同一个对象

推荐使用:StringBuffer

  • 如果你有多个线程同时操作一个 SQL 构建器,可以用 StringBuffer 来保证线程安全。
  • 实际开发中这种情况较少见。
StringBuffer sql = new StringBuffer("INSERT INTO logs VALUES ");

someDataList.forEach(data -> {
    sql.append("(").append(data).append("),");
});

sql.deleteCharAt(sql.length() - 1); // 删除最后一个逗号

情况三:使用 String 拼接 SQL

不推荐使用

  • String 是不可变对象,每次拼接都会创建新对象,效率低。
  • 特别是在循环或条件判断中频繁拼接时,性能下降明显。
String sql = "SELECT * FROM users";
if (name != null) {
    sql += " AND name = '" + name + "'"; // 每次都会创建新 String 对象
}

虽然代码看起来简洁,但性能较差,尤其在大量拼接时。


⚠️ 注意事项(SQL 拼接常见问题)

  1. 防止 SQL 注入
    • 不要直接拼接用户输入的参数,应使用 PreparedStatement 替代字符串拼接。
    • 示例:
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, name);
  1. 避免手动拼接复杂 SQL
    • 使用 MyBatis、JPA、QueryDSL 等 ORM 工具可以更安全地构建 SQL。
    • 或者使用 WHERE 1=1 技巧配合 StringBuilder 动态拼接条件。
  2. 注意结尾多余的 AND / OR / ,
    • 建议使用标志位或 delete 方法清除多余字符。

总结:如何选择?

场景推荐类说明
单线程拼接 SQLStringBuilder最常用,性能好
多线程拼接 SQLStringBuffer线程安全
快速简单拼接⚠️ String不建议,容易导致性能问题
安全构建 SQLPreparedStatement更推荐,防注入

StringBuider&&StringBuffer的区别

理解 StringBuilderStringBuffer 之间的区别,尤其是它们在多线程环境下的行为差异,对于选择合适的数据结构至关重要。下面我将详细解释这两者的主要区别,特别是关于它们在多线程环境中的使用。

线程安全 vs 非线程安全

  • StringBuilder:是非线程安全的。这意味着它没有内置的同步机制来防止多个线程同时访问和修改同一个 StringBuilder 实例。因此,在单线程环境中使用 StringBuilder 更加高效,因为它避免了同步带来的性能开销。
  • StringBuffer:是线程安全的。它的所有公共方法都是同步的(使用 synchronized 关键字),这确保了即使在多线程环境下也能正确地操作字符串。然而,这种同步也带来了额外的性能成本,因为每次调用方法时都需要获取锁,这可能降低执行速度。

性能影响

由于 StringBuffer 的方法都是同步的,当多个线程试图同时访问或修改 StringBuffer 对象时,这些线程会被串行化处理(即一个接一个地执行),从而降低了整体性能。相比之下,StringBuilder 不提供任何同步措施,因此在单线程环境中可以更快地执行字符串拼接等操作。

使用场景

  • StringBuilder:适用于单线程环境或当你确定不会在多线程环境中共享该对象时。例如,在构建 SQL 查询、格式化输出或其他需要频繁进行字符串操作的地方。
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // 输出 "Hello World"
  • StringBuffer:适用于多线程环境,或者你需要在线程间共享可变字符串缓冲区的情况。不过,如果确实需要在多线程中使用 StringBuffer,请考虑是否真的需要这样做,因为很多时候可以通过其他方式(如局部变量)避免共享状态。
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // 输出 "Hello World"

多线程示例

假设我们有一个简单的例子,其中多个线程尝试同时向同一个字符串追加内容:

public class AppendRunnable implements Runnable {
    private final StringBuilder builder;

    public AppendRunnable(StringBuilder builder) {
        this.builder = builder;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            builder.append("a");
        }
    }
}

如果我们使用 StringBuilder 并让两个线程同时运行这个 Runnable,可能会得到不可预测的结果,因为没有同步保护。但是,如果我们将 StringBuilder 替换为 StringBuffer,那么每个对 append 方法的调用都会被同步,从而保证了线程安全。

public class AppendRunnable implements Runnable {
    private final StringBuffer buffer;

    public AppendRunnable(StringBuffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.append("a");
        }
    }
}

在这个例子中,尽管 StringBuffer 提供了线程安全性,但在实际应用中,除非确实需要在多个线程之间共享 **StringBuffer** 实例,否则更推荐使用 StringBuilder 来获得更好的性能。

结论

  • 单线程环境中,你应该优先选择 StringBuilder,因为它提供了更好的性能。
  • 如果你处于多线程环境,并且需要在多个线程之间共享同一个可变字符串实例,则应该使用 StringBuffer

大多数情况下,尤其是在编写 Web 应用程序时,不需要担心多线程问题,因为每个请求通常由单独的线程处理。在这种情况下,StringBuilder 是更合适的选择。如果应用场景涉及到并发编程并且需要共享可变字符串缓冲区,那么才需要考虑使用 StringBuffer


public class StringBuilderExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个 StringBuilder 实例
        final StringBuilder builder = new StringBuilder();

        // 创建并启动多个线程,每个线程都向 builder 追加内容
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                builder.append("a");
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                builder.append("b");
            }
        });

        t1.start();
        t2.start();

        // 等待所有线程完成
        t1.join();
        t2.join();

        // 输出最终的字符串长度和内容
        System.out.println("Final length: " + builder.length());
        System.out.println("Final string: " + builder.toString());
    }
}

预期结果:理想情况下,最终的字符串应该包含 2000 个字符(1000 个 ‘a’ 和 1000 个 ‘b’)。然而,由于 StringBuilder 是非线程安全的,两个线程可能相互干扰,导致最终的结果不一致或出现异常。

public class StringBufferExample { public static void main(String[] args) throws InterruptedException {
 // 创建一个 StringBuffer 实例
 final StringBuffer buffer = new StringBuffer();

 // 创建并启动多个线程,每个线程都向 buffer 追加内容
 Thread t1 = new Thread(() -> {
     for (int i = 0; i < 1000; i++) {
         buffer.append("a");
     }
 });

 Thread t2 = new Thread(() -> {
     for (int i = 0; i < 1000; i++) {
         buffer.append("b");
     }
 });

 t1.start();
 t2.start();

 // 等待所有线程完成
 t1.join();
 t2.join();

 // 输出最终的字符串长度和内容
 System.out.println("Final length: " + buffer.length());
 System.out.println("Final string: " + buffer.toString());
 }
}

预期结果:由于 StringBuffer 的方法是同步的,所以即使两个线程同时运行,它们也不会相互干扰,最终的字符串将包含 2000 个字符(1000 个 ‘a’ 和 1000 个 ‘b’),并且顺序正确。

posted @ 2025-06-24 15:02  柯基大大  阅读(6)  评论(0)    收藏  举报  来源