Java String Concatenation Performance

Hello anh em!
Ở bài trước mình đã giới thiệu các bạn cách tạo một project JMH với Gradle. Bài này mình sẽ so sánh hiệu năng các cách nối chuỗi trong Java.

Cuối bài mình có show một report như sau:

BenchmarkModeCntScoreErrorUnits
StringAppend.bmStringBufferthrpt513566863.27±427443.554ops/s
StringAppend.bmStringBufferx0005thrpt55529261.499±277625.096ops/s
StringAppend.bmStringBufferx1000thrpt536998.492±2895.325ops/s
StringAppend.bmStringBuilderthrpt513462457.33±978240.749ops/s
StringAppend.bmStringBuilderx0005thrpt55466705.447±176616.966ops/s
StringAppend.bmStringBuilderx1000thrpt536989.126±1716.705ops/s
StringAppend.bmStringConcatthrpt527930719.3±581249.324ops/s
StringAppend.bmStringConcatx0005thrpt57170529.805±383608.303ops/s
StringAppend.bmStringConcatx1000thrpt547104.039±2358.121ops/s
StringAppend.bmStringJointhrpt510204201.92±463528.75ops/s
StringAppend.bmStringJoinx0005thrpt54322622.068±49032.328ops/s
StringAppend.bmStringJoinx1000thrpt530493.885±1708.261ops/s
StringAppend.bmStringPlusthrpt513689364.1±509784.615ops/s
StringAppend.bmStringPlusx0005thrpt55590364.456±277476.212ops/s
StringAppend.bmStringPlusx1000thrpt5288.086±15.175ops/s

đây là kết quả về so sánh hiệu năng các cách nối chuỗi trong Java.

Và giờ mình sẽ làm giải thích vì sao lại có kết quả như vậy.

1. String Plus (Toán tử cộng)

Đây là cách nối chuỗi phổ biến nhất trong Java. Ví dụ:

1
2
String str = "Hello ";
str += "world";

dòng 1 sẽ tạo ra 1 đối tượng String có giá trị “Hello”, dòng 2 sẽ tạo một String khác có giá trị “Hello world”, nhưng trước khi tạo ra 1 String mới Java sẽ kiểm tra đối tượng đó đã ở trong String Poll hay không. Trường hợp có sẽ không tạo ra String mới mà dùng String trong pool, nếu không có sẽ tạo ra String mới và thêm vào pool (mình sẽ có bài nói chi tiết hơn).

Như kết quả mình show thì nó cách này có hiệu năng tốt ở x1 và x5 nhưng tệ nhất với x1000. Vì khi này Java sẽ chỉ tạo 1 vài String và sẽ không làm ảnh hưởng tới performance với số lần lặp lớn.

2. String Concat

So sánh với String Plus thì tốc độ chênh lệch rất lớn. Vậy tại sao mà có chênh lệch lớn thế? Ta cùng xem code nhé:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}

Dòng code trên sẽ tạo ra 1 String mới bằng cách copy char array của 2 String trên. Như thế sẽ không có sự tham gia của String Pool và sẽ tiết kiệm được kha khá chi phí.

3. String Builder

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

Ở trên là 2 phương thức sẽ được gọi khi ta thực hiện phương thức append(). Như ta thấy tinh thần cũng giống với concat ngoại trừ việc trả về AbstractStringBuilder thay vì tạo ra một String mới

4. String Buffer

String Buffer là phiên bản Thread safe của String Builder nên việc kết quả chậm hơn là hiển nhiên. Ta có thể xem qua code sau để hiểu cách implement thread safe:

1
2
3
4
5
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}

Từ khóa synchronized chỉ định trong một thời điểm chỉ có một thread có thể thực hiện được method, và nhờ có nó sẽ đảm bảo thread safe.

5. String Join

Kết luận:

Nếu còn thiếu cách nào anh em có thể comment hoặc là tạo merge request để mình update thêm nhé ; ))

updatedupdated2020-10-222020-10-22
Load Comments?