Java Foundations
16/39
Bài 16 / 39~12 phútCú pháp Java & Kiểu dữ liệuLộ trình · chặng 02/21Miễn phí… lượt xem

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íStringStringBuilderText 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ênCần escape \nNối từng dòng thủ côngHỗ trợ cú pháp native
Kịch bản sử dụng chínhDữ liệu văn bản cố địnhNối chuỗi động trong vòng lặpMẫ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:

Java
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 """:

Java
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.
Java
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:

Java
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ệuKiểu dữ liệu tương ứngVí dụ kết quả
%sChuỗi văn bản bất kỳ"OLHub"
%dSố nguyên (int, long)42
%.2fSố thực làm tròn 2 chữ số thập phân3.14
%nKý tự xuống dòng độc lập nền tảngXuố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

Java
// 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)$:

Java
// 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

Hai khung so sánh cùng một vòng lặp: bên trái String cộng dồn phải cấp chuỗi mới và copy toàn bộ ký tự đã có mỗi vòng, bên phải StringBuilder chỉ ghi thêm vào ô trống của buffer và gọi toString đúng một lần ở cuối

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ăngVí 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ân biệt .trim() và .strip()

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óa synchronized trê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?

Java
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?

📚 Deep Dive Oracle — đặc tả chính thức
  • 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?

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Vì 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)$?
  2. Q2
    Dấ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?
  3. Q3
    Điểm khác biệt quan trọng giữa phương thức .trim() và .strip() là gì?
  4. Q4
    Khi nào lập trình viên NÊN sử dụng StringBuffer thay vì StringBuilder?
  5. Q5
    Trì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.

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

Bài tiếp theo

Hằng số và final — giá trị khắc trên đá