Tác giả: Lê Trung Kiên lớp java 08
Email: lekien.2803.cg@gmail.com
SĐT: 0942096947
Link bài gốc: https://www.baeldung.com/java-string-builder-string-buffer

1. Tổng quan

Trong bài viết ngắn này chúng ta sẽ cùng xem xét qua điểm khác biệt và tương đồng giữa StringBuilder và StringBuffer nhé. Trong Java 1.5, StringBuilder đã được giới thiệu để thay thế cho StringBuffer.

2. Điểm tương đồng

Cả StringBuilder và StringBuffer đều tạo ra các đối tượng chứa một chuỗi ký tự có thể thay đổi. Hãy xem cách nó hoạt động và so sánh nó với một class String bất biến:

String immutable = "abc";
immutable = immutable + "def";

Mặc dù có vẻ như chúng ta đang sửa đổi cùng một đối tượng bằng cách nối thêm “def”, nhưng chúng ta đang tạo một đối tượng mới vì không thể sửa được chuỗi cũ, vì String trong Java là bất biến.
Các bạn có thể tham khảo thêm bài viết về sự bất biến của String qua bài viết này.

Khi sử dụng StringBuffer hoặc StringBuilder, chúng ta có thể sử dụng phương thức append():

StringBuffer sb = new StringBuffer("abc");
sb.append("def");

Trong trường hợp này, không có đối tượng mới nào được tạo thêm. Chúng ta đã gọi phương thức append() trên sb instance và sửa đổi nội dung của nó. StringBuffer và StringBuilder đều là các đối tượng có thể thay đổi, không phải bất biến.

3. Điểm khác biệt

StringBuffer được đồng bộ hóa do đó nó an toàn cho các luồng (thread-safe). StringBuilder tương thích với StringBuffer API nhưng không đảm bảo đồng bộ hóa.
Bởi vì StringBuilder không đồng bộ hóa, nên nó nhanh hơn và được sử dụng ở môi trường không đa luồng.

3.1. Hiệu suất

Trong các lần lặp lại nhỏ, sự khác biệt về hiệu suất là không đáng kể. Hãy thực hiện micro-benchmark với JMH:

@State(Scope.Benchmark)
public static class MyState {
    int iterations = 1000;
    String initial = "abc";
    String suffix = "def";
}

@Benchmark
public StringBuffer benchmarkStringBuffer(MyState state) {
    StringBuffer stringBuffer = new StringBuffer(state.initial);
    for (int i = 0; i < state.iterations; i++) {
        stringBuffer.append(state.suffix);
    }
    return stringBuffer;
}

@Benchmark
public StringBuilder benchmarkStringBuilder(MyState state) {
    StringBuilder stringBuilder = new StringBuilder(state.initial);
    for (int i = 0; i < state.iterations; i++) {
        stringBuilder.append(state.suffix);
    }
    return stringBuilder;
}

Sử dụng chế độ Throughput mặc định – tức là các hoạt động trên mỗi đơn vị thời gian (điểm càng cao càng tốt):

Benchmark                                          Mode  Cnt      Score      Error  Units
StringBufferStringBuilder.benchmarkStringBuffer   thrpt  200  86169.834 ±  972.477  ops/s
StringBufferStringBuilder.benchmarkStringBuilder  thrpt  200  91076.952 ± 2818.028  ops/s

Nếu chúng ta tăng số lần lặp từ 1k lên 1m thì chúng ta sẽ nhận được:

Benchmark                                          Mode  Cnt   Score   Error  Units
StringBufferStringBuilder.benchmarkStringBuffer   thrpt  200  77.178 ± 0.898  ops/s
StringBufferStringBuilder.benchmarkStringBuilder  thrpt  200  85.769 ± 1.966  ops/s

Tuy nhiên, hãy nhớ rằng đây là micro-benchmark , có thể có hoặc không có tác động thực sự đến hiệu suất thực khi chạy một ứng dụng trong thực tế.

4. Kết luận

Nói túm lại, StringBuffer đồng bộ và an toàn cho đa luồng, nên nó chậm hơn StringBuilder.
Còn trong môi trường đơn luồng, ta nên sử dụng StringBuilder.