String, Text Block và StringBuilder trong Java hiện đại
Tìm hiểu String immutable, Text Block đa dòng từ Java 15 và StringBuilder mutable để tối ưu hiệu năng nối chuỗi, phòng tránh bẫy độ phức tạp O(n²).
TL;DR: Trong Java, String là bất biến (immutable), mọi thao tác sửa đổi đều cấp phát đối tượng mới trên Heap. Việc cộng chuỗi bằng toán tử += trong vòng lặp lớn sẽ gây suy giảm hiệu năng nghiêm trọng với độ phức tạp $O(n^2)$; giải pháp bắt buộc là sử dụng StringBuilder với buffer có thể biến đổi. Từ Java 15, Text Block ("""...""") cho phép khai báo chuỗi nhiều dòng nguyên bản cho JSON, SQL hay HTML mà không cần escape ký tự xuống dòng.
Bài 1 đã giới thiệu String như kiểu tham chiếu phổ biến nhất. Bài học này đi sâu vào bộ 3 công cụ xử lý văn bản trong Java: khi nào dùng String chuẩn, khi nào tận dụng Text Block cho văn bản nhiều dòng, và khi nào bắt buộc phải dùng StringBuilder để bảo vệ hiệu năng hệ thống.
1. Analogy máy đánh chữ và bảng viết tay mô tả công cụ chuỗi ra sao?
Hãy hình dung 3 công cụ xử lý chuỗi qua các vật dụng thực tế:
- Máy đánh chữ (
String): Khi chữ đã in lên giấy, nội dung không thể tẩy xóa. Muốn sửa một từ, bạn phải lấy một tờ giấy mới và gõ lại từ đầu. - Bảng viết tay (
StringBuilder): Cho phép viết thêm, sửa đổi hoặc xóa bớt trực tiếp trên cùng một bề mặt mà không cần thay bảng mới. - Mẫu in sẵn đa dòng (
Text Block): Giữ nguyên bố cục nhiều dòng của tài liệu mẫu như hợp đồng, JSON hay câu lệnh SQL mà không cần đánh dấu ngắt dòng thủ công.
2. Ba công cụ xử lý văn bản trong Java khác biệt nhau như thế nào?
| Tiêu chí | String | StringBuilder | Text Block (""") |
|---|---|---|---|
| Khả năng biến đổi (Mutability) | Bất biến (Immutable) | Có thể biến đổi (Mutable) | Bất biến (Immutable) |
| Hỗ trợ đa dòng tự nhiên | Cần escape \n | Nối từng dòng thủ công | Hỗ trợ cú pháp native |
| Kịch bản sử dụng chính | Dữ liệu văn bản cố định | Nối chuỗi động trong vòng lặp | Mẫu câu lệnh SQL, JSON, HTML |
3. Tại sao String trong Java lại bất biến (immutable)?
Khi một đối tượng String được khởi tạo, nội dung ký tự của nó được bảo vệ cố định trong bộ nhớ. Mọi phương thức biến đổi như .toUpperCase(), .substring(), hay .replace() đều trả về một đối tượng String hoàn toàn mới:
String s = "hello";
String upper = s.toUpperCase(); // Tao String moi "HELLO"
System.out.println(s); // "hello" -- chuoi goc giu nguyen
System.out.println(upper); // "HELLO"
Tính bất biến mang lại độ an toàn tuyệt đối cho ứng dụng đa luồng và cho phép JVM tối ưu bộ nhớ thông qua String Pool. Tuy nhiên, nếu lạm dụng phép cộng chuỗi trong vòng lặp, tính bất biến sẽ trở thành cái bẫy hiệu năng.
4. Cú pháp Text Block giữ nguyên định dạng đa dòng hoạt động ra sao?
Text Block (chuẩn hóa từ Java 15 qua JEP 378) mở và đóng bằng 3 dấu ngoặc kép """:
String query = """
SELECT u.id, u.email
FROM users u
WHERE u.active = true
ORDER BY u.id DESC
""";
Quy tắc định dạng Text Block
- Ký tự mở
"""phải đứng riêng trên một dòng, không chứa văn bản phía sau. - Thuật toán biên dịch tự động loại bỏ khoảng trắng chung (common indentation) dựa trên cột thụt lề của dấu đóng
""". - Không cần escape dấu ngoặc kép đơn
"bên trong chuỗi.
String jsonPayload = """
{
"name": "%s",
"status": "ACTIVE"
}
""".formatted("OLHub User");
5. Các phương thức định dạng chuỗi hoạt động như thế nào?
Java cung cấp nhiều cách thức định dạng chuỗi động tiện lợi sử dụng format specifiers:
String name = "An";
int age = 25;
double score = 9.25;
// Dinh dang qua String.format truyen thong
String info = String.format("Hoc vien: %s, Tuoi: %d, Diem: %.2f", name, age, score);
// Dinh dang truc tiep qua method .formatted() (Java 15+)
String modern = "Hoc vien: %s, Tuoi: %d".formatted(name, age);
System.out.println(info);
System.out.println(modern);
| Ký hiệu | Kiểu dữ liệu tương ứng | Ví dụ kết quả |
|---|---|---|
%s | Chuỗi văn bản bất kỳ | "OLHub" |
%d | Số nguyên (int, long) | 42 |
%.2f | Số thực làm tròn 2 chữ số thập phân | 3.14 |
%n | Ký tự xuống dòng độc lập nền tảng | Xuống dòng chuẩn OS |
6. Tại sao nối chuỗi trong vòng lặp cần đến StringBuilder?
Cái bẫy độ phức tạp $O(n^2)$ khi dùng toán tử cộng chuỗi
// KHONG NEN: gay ton bo nho va cham tram trong
String result = "";
for (int i = 0; i < 10_000; i++) {
result += "x"; // Moi lan lap phai cap phat mang moi va copy toan bo du lieu cu
}
Ở mỗi vòng lặp, JVM phải phân bổ một mảng mới và sao chép lại toàn bộ các ký tự trước đó. Với 10.000 vòng lặp, tổng số thao tác sao chép ký tự lên tới n(n - 1) / 2 ≈ 50 triệu thao tác.
Tối ưu hóa với StringBuilder
StringBuilder sở hữu buffer mảng ký tự nội bộ có khả năng tự động mở rộng dung lượng khi cần. Thao tác thêm ký tự vào cuối đạt độ phức tạp amortized $O(1)$:
// DUNG: toc do thuc thi toi uu O(n)
StringBuilder sb = new StringBuilder(10_000); // Cap phat buffer truoc neu biet kich thuoc
for (int i = 0; i < 10_000; i++) {
sb.append('x'); // Ghi truc tiep vao o nho tiep theo cua buffer
}
String output = sb.toString(); // Chi tao doi tuong String dung mot lan duy nhat

7. Các phương thức cốt lõi của String thường dùng gồm những gì?
| Phương thức | Ý nghĩa chức năng | Ví dụ minh họa |
|---|---|---|
.length() | Trả về số lượng UTF-16 code units | "Java".length() $\to$ 4 |
.isBlank() | Kiểm tra chuỗi rỗng hoặc chỉ toàn khoảng trắng | " ".isBlank() $\to$ true |
.strip() | Loại bỏ khoảng trắng Unicode đầu và cuối | " hi ".strip() $\to$ "hi" |
.substring(from, to) | Trích xuất chuỗi con từ chỉ số bắt đầu tới kết thúc | "Platform".substring(0, 4) $\to$ "Plat" |
.contains(sub) | Kiểm tra sự tồn tại của chuỗi con | "Java".contains("av") $\to$ true |
.equals(other) | So sánh bằng theo nội dung ký tự | "A".equals("A") $\to$ true |
Phương thức .trim() từ Java 1.0 chỉ nhận diện khoảng trắng có mã ASCII $\le 32$. Với các ký tự khoảng trắng Unicode hiện đại (như khoảng trắng tiếng Nhật hoặc non-breaking space), hãy luôn dùng .strip() từ Java 11+.
8. Khi nào nên dùng StringBuffer thay vì StringBuilder?
StringBuilder: Không đồng bộ (non-thread-safe), tốc độ tối ưu, là lựa chọn chuẩn cho 99% các trường hợp xử lý chuỗi đơn luồng cục bộ.StringBuffer: Có từ Java 1.0, chứa từ khóasynchronizedtrên các phương thức công khai. Chỉ sử dụng khi đối tượng chuỗi khả biến được chia sẻ trực tiếp giữa nhiều luồng cạnh tranh mà không có cơ chế khóa ngoài.
9. Mã nguồn ví dụ so sánh hiệu năng nối chuỗi hoạt động như thế nào?
public class StringPerformanceComparison {
public static void main(String[] args) {
int iterations = 20_000;
// Do thoi gian String concat thu cong
long startConcat = System.currentTimeMillis();
String text = "";
for (int i = 0; i < iterations; i++) {
text += "a";
}
long durationConcat = System.currentTimeMillis() - startConcat;
// Do thoi gian StringBuilder
long startBuilder = System.currentTimeMillis();
StringBuilder builder = new StringBuilder(iterations);
for (int i = 0; i < iterations; i++) {
builder.append('a');
}
String result = builder.toString();
long durationBuilder = System.currentTimeMillis() - startBuilder;
System.out.println("Thoi gian String concat: " + durationConcat + " ms");
System.out.println("Thoi gian StringBuilder: " + durationBuilder + " ms");
System.out.println("Do dai ket qua: " + result.length());
}
}
10. Tài liệu Deep Dive Oracle quy định gì về String và Text Block?
- JLS §3.10.6 — Text Blocks: Đặc tả cú pháp và thuật toán chuẩn hóa khoảng trắng thụt đầu dòng.
- JEP 378 — Text Blocks: Mục tiêu thiết kế chuỗi đa dòng và cơ chế bảo vệ ký tự escape.
- String API Specs: Chi tiết các phương thức xử lý ký tự và giải thích tính bất biến.
11. Điểm cốt lõi cần nhớ về xử lý chuỗi trong Java là gì?
- String bất biến: Không bao giờ cộng dồn chuỗi bằng
+=trong các vòng lặp lớn để tránh suy giảm hiệu năng. - StringBuilder: Luôn khởi tạo và dùng
.append()khi cần ghép nhiều mảnh văn bản động. - Text Block: Sử dụng cú pháp
"""để giữ nguyên định dạng trực quan cho truy vấn SQL, JSON hoặc HTML. - Xử lý khoảng trắng: Ưu tiên
.strip()và.isBlank()từ Java 11 thay cho các phương thức cũ.
12. Tự kiểm tra kiến thức về String và Text Block như thế nào?
- Q1Vì sao thao tác
result += "x"trong vòng lặp 10.000 lần lại có độ phức tạp thời gian là $O(n^2)$? - Q2Dấu đóng
"""trong Text Block quyết định yếu tố định dạng nào của toàn bộ đoạn văn bản? - Q3Điểm khác biệt quan trọng giữa phương thức
.trim()và.strip()là gì? - Q4Khi nào lập trình viên NÊN sử dụng
StringBufferthay vìStringBuilder? - Q5Trình biên dịch Java có tự động tối ưu hóa biểu thức
String s = a + b + c;bên ngoài vòng lặp không?
Bài tiếp theo: Hằng số và final — giá trị khắc trên đá
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Ép kiểu trong Java: Widening, Narrowing và Safe Cast
Bài tiếpHằng số và final — giá trị khắc trên đá
Lộ trình Junior Java Backend Developer
Chặng 2/21 · Lập trình từ số 0 với Java — bài 16/32 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này