String là một trong những lớp được sử dụng rộng rãi nhất trong Java. StringBuffer và StringBuilder cung cấp các phương thức để thao tác với chuỗi. Chúng ta sẽ tìm hiểu sự khác biệt giữa sự khác biệt giữa String, StringBuffer và StringBuilder. Nên dùng StringBuffer hay StringBuilder cũng là một câu hỏi phổ biến trong phỏng vấn Java.

String, StringBuffer và StringBuilder
String là một trong những chủ đề quan trọng nhất trong phỏng vấn core Java. Nếu cần viết một chương trình in dữ liệu ra console, bạn sẽ dùng String. Hướng dẫn này tập trung vào những đặc điểm chính của lớp String, sau đó so sánh với các lớp StringBuffer và StringBuilder.
String trong Java
- Lớp String đại diện cho chuỗi ký tự, có thể khởi tạo theo 2 cách sau:
String str = "ABC";
// hoặc
String str = new String("ABC");
- String là immutable trong Java, rất phù hợp với môi trường đa luồng. Chuỗi có thể chia sẻ giữa nhiều hàm mà không cần phải bận tâm về vấn đề nhất quán dữ liệu.
- Khi tạo một String bằng dấu nháy kép, JVM sẽ kiểm tra trước trong string pool xem đã tồn tại chuỗi có cùng giá trị hay chưa. Nếu có, nó trả về tham chiếu của đối tượng trong pool. Nếu chưa, JVM tạo mới đối tượng String trong pool rồi trả về tham chiếu. Cách này giúp JVM tiết kiệm bộ nhớ hiệu quả khi chúng ta cần dùng một chuỗi ở nhiều luồng khác nhau.
- Nếu dùng toán tử
newđể tạo chuỗi, đối tượng sẽ được tạo trong heap memory. - Toán tử
+được nạp chồng cho String. Có thể dùng để nối 2 chuỗi, mặc dù bên trong JVM thực hiện thao tác này bằng StringBuffer. - String ghi đè các phương thức
equals()vàhashCode(). Hai String chỉ bằng nhau khi có cùng chuỗi ký tự. Phương thứcequals()phân biệt chữ hoa và chữ thường. Nếu muốn so sánh mà không phân biệt chữ hoa và chữ thường, bạn nên dùngequalsIgnoreCase(). - String sử dụng mã hóa UTF-16 cho luồng ký tự.
- String là một lớp final. Tất cả các trường đều final trừ
private int hash. Trường này lưu giá trị của hàmhashCode(). Giá trị hash chỉ được tính khi phương thứchashCode()được gọi lần đầu, sau đó lưu lại trong trường này. Hash được tạo dựa trên các trường final của lớp String cùng một số phép tính. Vì vậy mỗi lần gọihashCode(), kết quả trả về luôn giống nhau. Với người gọi, có vẻ như việc tính toán được thực hiện lại trong mỗi lần gọi phương thức nhưng thực tế giá trị đã được lưu sẵn trong trườnghash.
String vs StringBuffer
Vì String là immutable trong Java, nên mỗi khi thực hiện thao tác như nối chuỗi (concatenation), tách chuỗi (substring),… JVM sẽ tạo ra một String mới và bỏ String cũ vào garbage collection. Đây là các thao tác tiêu tốn nhiều tài nguyên xử lý và tạo ra nhiều đối tượng rác trong heap.
Để khắc phục, Java cung cấp các lớp StringBuffer và StringBuilder dùng cho việc thao tác chuỗi. StringBuffer và StringBuilder là các đối tượng mutable trong Java, cung cấp các phương thức append(), insert(), delete() và substring() để xử lý chuỗi.
StringBuffer vs StringBuilder
Trước Java 1.4, StringBuffer là giải pháp thao tác với chuỗi duy nhất trên Java. Tuy nhiên, nhược điểm của nó là tất cả các phương thức public đều được đồng bộ hóa. StringBuffer đảm bảo an toàn theo luồng (thread-safety) nhưng phải đánh đổi bằng hiệu năng.
Trong hầu hết trường hợp, chuỗi không được dùng trong môi trường đa luồng, vì thế Java 1.5 đã giới thiệu lớp StringBuilder, có chức năng tương tự StringBuffer nhưng không có cơ chế đồng bộ hóa và thread-safety.
StringBuffer có thêm một số phương thức như substring, length, capacity, trimToSize…, tuy nhiên chúng không thực sự cần thiết vì đã có sẵn trong lớp String. Chính vì vậy các phương thức này không được triển khai trong lớp StringBuilder.
StringBuffer ra mắt từ Java 1.0, còn StringBuilder chỉ được bổ sung ở Java 1.5 để khắc phục những hạn chế của StringBuffer. Trong môi trường đơn luồng hoặc khi không cần đến thread safety, bạn nên dùng StringBuilder. Ngược lại, nếu yêu cầu thao tác an toàn theo luồng thì hãy chọn StringBuffer.
StringBuilder vs StringBuffer Performance
Để kiểm tra ảnh hưởng của cơ chế đồng bộ hóa đến hiệu năng, chúng ta sẽ dùng một chương trình mẫu, trong đó thực hiện nhiều lần gọi append() trên đối tượng StringBuffer và StringBuilder.
package com.journaldev.java;
import java.util.GregorianCalendar;
public class TestString {
public static void main(String[] args) {
System.gc();
long start=new GregorianCalendar().getTimeInMillis();
long startMemory=Runtime.getRuntime().freeMemory();
StringBuffer sb = new StringBuffer();
//StringBuilder sb = new StringBuilder();
for(int i = 0; i<10000000; i++){
sb.append(":").append(i);
}
long end=new GregorianCalendar().getTimeInMillis();
long endMemory=Runtime.getRuntime().freeMemory();
System.out.println("Time Taken:"+(end-start));
System.out.println("Memory used:"+(startMemory-endMemory));
}
}
Đoạn code trên cũng được chạy với đối tượng StringBuffer để so sánh thời gian và bộ nhớ sử dụng. Mỗi trường hợp được thực thi 5 lần và sau đó tính giá trị trung bình.
| Giá trị i | StringBuffer (Time, Memory) | StringBuilder (Time, Memory) |
|---|---|---|
| 10,00,000 | 808, 149356704 | 633, 149356704 |
| 1,00,00,000 | 7448, 147783888 | 6179, 147783888 |
Kết quả cho thấy StringBuilder cho hiệu năng tốt hơn StringBuffer ngay cả trong môi trường đơn luồng. Nguyên nhân chính của sự khác biệt này là do các phương thức trong StringBuffer đều được đồng bộ hóa.
Sự khác biệt giữa String, StringBuffer và StringBuilder
- String là immutable trong khi StringBuffer và StringBuilder là các lớp mutable.
- StringBuffer hỗ trợ an toàn luồng và có cơ chế đồng bộ hóa, còn StringBuilder thì không, vì thế StringBuilder có tốc độ xử lý nhanh hơn StringBuffer.
- Toán tử nối chuỗi (
+) trong nội bộ thực chất sử dụng lớp StringBuffer hoặc StringBuilder. - Khi thao tác với chuỗi trong môi trường đơn luồng, chúng ta nên dùng StringBuilder, còn nếu cần an toàn luồng thì hãy dùng StringBuffer.
Trên đây là phần tổng kết nhanh sự khác biệt giữa String, StringBuffer và StringBuilder. Trong hầu hết các tình huống lập trình phổ biến, StringBuilder phù hợp hơn so với StringBuffer.