【JavaSE】String,StringBuider,StringBuffer
文章目錄
拼接 SQL 語句怎么做
在 Java 中,如果你要拼接 SQL 語句(比如動態生成查詢或插入語句),選擇使用 String、StringBuilder 還是 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 拼接常見問題)
- 防止 SQL 注入
- 不要直接拼接用戶輸入的參數,應使用
PreparedStatement替代字符串拼接。 - 示例:
- 不要直接拼接用戶輸入的參數,應使用
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, name);
- 避免手動拼接復雜 SQL
- 使用 MyBatis、JPA、QueryDSL 等 ORM 工具可以更安全地構建 SQL。
- 或者使用
WHERE 1=1技巧配合StringBuilder動態拼接條件。
- 注意結尾多余的 AND / OR / ,
- 建議使用標志位或
delete方法清除多余字符。
- 建議使用標志位或
總結:如何選擇?
| 場景 | 推薦類 | 說明 |
|---|---|---|
| 單線程拼接 SQL | ? StringBuilder | 最常用,性能好 |
| 多線程拼接 SQL | ? StringBuffer | 線程安全 |
| 快速簡單拼接 | ?? String | 不建議,容易導致性能問題 |
| 安全構建 SQL | ? PreparedStatement | 更推薦,防注入 |
StringBuider&&StringBuffer的區別
理解 StringBuilder 和 StringBuffer 之間的區別,尤其是它們在多線程環境下的行為差異,對于選擇合適的數據結構至關重要。下面我將詳細解釋這兩者的主要區別,特別是關于它們在多線程環境中的使用。
線程安全 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’),并且順序正確。

浙公網安備 33010602011771號