StringBuilder vs String concatenation in toString () trong Java


925

Đưa ra 2 toString()triển khai dưới đây, cái nào được ưu tiên:

public String toString(){
    return "{a:"+ a + ", b:" + b + ", c: " + c +"}";
}

hoặc là

public String toString(){
    StringBuilder sb = new StringBuilder(100);
    return sb.append("{a:").append(a)
          .append(", b:").append(b)
          .append(", c:").append(c)
          .append("}")
          .toString();
}

?

Quan trọng hơn, do chúng ta chỉ có 3 thuộc tính, nó có thể không tạo ra sự khác biệt, nhưng tại thời điểm nào bạn sẽ chuyển từ +concat sang StringBuilder?


40
Tại thời điểm nào bạn chuyển sang StringBuilder? Khi nó ảnh hưởng đến bộ nhớ hoặc hiệu suất. Hoặc khi nó có thể. Nếu bạn thực sự chỉ làm điều này cho một vài chuỗi một lần, không phải lo lắng. Nhưng nếu bạn sẽ làm đi làm lại nhiều lần, bạn sẽ thấy sự khác biệt có thể đo lường được khi sử dụng StringBuilder.
ewall

giá trị trung bình của 100 trong tham số là gì?
Asif Mushtaq

2
@UnKnown 100 là kích thước ban đầu của StringBuilder
không phải là trình tự tiếp theo 15/03/2016

@nonsequitor Vậy số ký tự tối đa sẽ là 100?
Asif Mushtaq

10
@ Không biết không chỉ kích thước ban đầu, nếu bạn biết kích thước gần đúng của chuỗi bạn đang xử lý thì bạn có thể cho biết StringBuilderkích thước phân bổ trả trước là bao nhiêu nếu không, nếu hết, sẽ phải tăng gấp đôi kích thước bằng cách tạo char[]mảng mới sau đó sao chép dữ liệu qua - đó là tốn kém. Bạn có thể gian lận bằng cách đưa ra kích thước và sau đó không cần tạo mảng này - vì vậy nếu bạn nghĩ chuỗi của bạn sẽ dài ~ 100 ký tự thì bạn có thể đặt StringBuilder thành kích thước đó và nó sẽ không bao giờ phải mở rộng bên trong.
không có trình tự 15/03/2016

Câu trả lời:


964

Phiên bản 1 thích hợp hơn vì nó ngắn hơn và trên thực tế trình biên dịch sẽ biến nó thành phiên bản 2 - không có sự khác biệt về hiệu năng.

Quan trọng hơn, chúng ta chỉ có 3 thuộc tính, nó có thể không tạo ra sự khác biệt, nhưng tại thời điểm nào bạn chuyển từ concat sang builder?

Tại thời điểm bạn nối vào một vòng lặp - đó thường là khi trình biên dịch không thể tự thay thế StringBuilder.


19
Điều đó đúng nhưng tham chiếu ngôn ngữ cũng nói rằng đây là tùy chọn. Trên thực tế, tôi vừa thực hiện một thử nghiệm đơn giản với JRE 1.6.0_15 và tôi không thấy bất kỳ tối ưu hóa trình biên dịch nào trong lớp dịch ngược.
bruno conde

37
Tôi vừa thử mã từ câu hỏi (được biên dịch trên JDK 1.6.0_16) và thấy tối ưu hóa như mong đợi. Tôi khá chắc chắn rằng tất cả các trình biên dịch hiện đại sẽ làm điều đó.
Michael Borgwardt

22
Bạn nói đúng. Nhìn vào mã byte tôi có thể thấy rõ tối ưu hóa StringBuilder. Tôi đã sử dụng một trình dịch ngược và, một số cách, nó đang chuyển đổi trở lại concat. +1
bruno conde

80
Không phải để đánh bại một con ngựa chết, nhưng từ ngữ trong thông số kỹ thuật là: To increase the performance of repeated string concatenation, a Java compiler _may_ use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.Từ khóa có thể có . Cho rằng điều này là chính thức tùy chọn (mặc dù rất có thể được thực hiện), chúng ta không nên tự bảo vệ mình?
Lucas

93
@Lika: Không, chúng ta không nên. Nếu trình biên dịch quyết định không thực hiện tối ưu hóa đó, thì đó sẽ là vì nó không xứng đáng. Trong 99% trường hợp, trình biên dịch biết rõ hơn việc tối ưu hóa nào đáng giá, do đó, theo quy tắc, nhà phát triển không nên can thiệp. Tất nhiên, tình huống của bạn có thể rơi vào 1% khác, nhưng điều đó chỉ có thể được kiểm tra bằng cách (cẩn thận) điểm chuẩn.
sleske

255

Điều quan trọng là liệu bạn có đang viết một kết nối duy nhất ở một nơi hay tích lũy nó theo thời gian.

Đối với ví dụ bạn đã đưa ra, không có điểm nào rõ ràng khi sử dụng StringBuilder. (Nhìn vào mã được biên dịch cho trường hợp đầu tiên của bạn.)

Nhưng nếu bạn đang xây dựng một chuỗi, ví dụ như bên trong một vòng lặp, hãy sử dụng StringBuilder.

Để làm rõ, giả sử rằng HugeArray chứa hàng ngàn chuỗi, mã như thế này:

...
String result = "";
for (String s : hugeArray) {
    result = result + s;
}

rất lãng phí thời gian và bộ nhớ so với:

...
StringBuilder sb = new StringBuilder();
for (String s : hugeArray) {
    sb.append(s);
}
String result = sb.toString();

3
Có, StringBuilder không cần phải tạo lại đối tượng String nhiều lần.
Olga

124
Khỉ thật, tôi đã sử dụng 2 chức năng đó để kiểm tra một chuỗi lớn tôi đang làm việc. 6.51 phút so với 11 giây
user1722791

1
Bằng cách này, bạn cũng có thể sử dụng result += s;(trong ví dụ đầu tiên)
RAnders00

1
Có bao nhiêu đối tượng sẽ được tạo ra bởi tuyên bố này? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
Asif Mushtaq

1
Điều gì về một cái gì đó như: Chuỗi str = (a == null)? null: a '+ (b == null)? null: b '+ (c == null)? c: c '+ ...; ? Điều đó sẽ ngăn chặn việc tối ưu hóa xảy ra?
amitfr

75

Tôi thích:

String.format( "{a: %s, b: %s, c: %s}", a, b, c );

... bởi vì nó ngắn và dễ đọc.

Tôi sẽ không tối ưu hóa điều này cho tốc độ trừ khi bạn sử dụng nó trong một vòng lặp với số lần lặp lại rất cao đã đo được sự khác biệt về hiệu suất.

Tôi đồng ý rằng nếu bạn phải xuất ra nhiều tham số, biểu mẫu này có thể gây nhầm lẫn (giống như một trong những ý kiến ​​nói). Trong trường hợp này, tôi sẽ chuyển sang một hình thức dễ đọc hơn (có lẽ sử dụng ToStringBuilder của apache-commons - được lấy từ câu trả lời của matt b) và bỏ qua hiệu suất một lần nữa.


64
Nó thực sự dài hơn, chứa nhiều ký hiệu hơn và có các biến văn bản nằm ngoài chuỗi.
Tom Hawtin - tackline

4
Vì vậy, bạn sẽ nói nó ít đọc hơn một trong những lời xin lỗi khác?
tangens

3
Tôi thích viết cái này hơn, bởi vì việc thêm nhiều biến dễ dàng hơn, nhưng tôi không chắc nó dễ đọc hơn - đặc biệt là khi số lượng đối số trở nên lớn hơn. Nó cũng không hoạt động cho những lúc bạn cần thêm bit vào những thời điểm khác nhau.
Alex Feinman

78
Có vẻ khó đọc hơn (đối với tôi). Bây giờ tôi phải quét qua lại giữa {...} và các tham số.
Steve Kuo

10
Tôi thích hình thức này hơn, vì nó an toàn nếu một trong các tham số lànull
rds

74

Trong hầu hết các trường hợp, bạn sẽ không thấy sự khác biệt thực tế giữa hai cách tiếp cận, nhưng thật dễ dàng để xây dựng một trường hợp xấu nhất như trường hợp này:

public class Main
{
    public static void main(String[] args)
    {
        long now = System.currentTimeMillis();
        slow();
        System.out.println("slow elapsed " + (System.currentTimeMillis() - now) + " ms");

        now = System.currentTimeMillis();
        fast();
        System.out.println("fast elapsed " + (System.currentTimeMillis() - now) + " ms");
    }

    private static void fast()
    {
        StringBuilder s = new StringBuilder();
        for(int i=0;i<100000;i++)
            s.append("*");      
    }

    private static void slow()
    {
        String s = "";
        for(int i=0;i<100000;i++)
            s+="*";
    }
}

Đầu ra là:

slow elapsed 11741 ms
fast elapsed 7 ms

Vấn đề là để + = nối vào một chuỗi tái tạo lại một chuỗi mới, do đó, nó có giá trị tuyến tính theo chiều dài của chuỗi của bạn (tổng của cả hai).

Vì vậy - với câu hỏi của bạn:

Cách tiếp cận thứ hai sẽ nhanh hơn, nhưng nó ít đọc và khó bảo trì hơn. Như tôi đã nói, trong trường hợp cụ thể của bạn, bạn có thể sẽ không thấy sự khác biệt.


Đừng quên .concat (). Tôi sẽ phỏng đoán thời gian đã trôi qua ở bất cứ đâu từ 10 đến 18 ms khiến nó không đáng kể khi sử dụng các chuỗi ngắn như ví dụ bài đăng gốc.
Dropo

9
Trong khi bạn nói đúng +=, ví dụ ban đầu là một chuỗi +, trình biên dịch chuyển thành một string.concatcuộc gọi duy nhất . Kết quả của bạn không áp dụng.
Blindy

1
@Blindy & Droo: - Cả hai bạn đều đúng. Sử dụng .concate trong kịch bản như vậy là cách giải quyết tốt nhất, vì + = tạo đối tượng mới mỗi khi thực hiện thường trình vòng lặp.
perilbrain

3
Bạn có biết rằng toString () của anh ấy không được gọi trong một vòng lặp không?
Omry Yadan

Tôi đã thử ví dụ này để kiểm tra tốc độ. Vì vậy, kết quả của tôi là: chậm trôi 29672 ms; trôi qua nhanh 15 ms. Vì vậy, câu trả lời là rõ ràng. Nhưng nếu nó là 100 lần lặp - thời gian là như nhau - 0 ms. Nếu 500 lần lặp - 16 ms và 0 ms. Và như thế.
Ernestas Gruodis

28

Tôi cũng đã đụng độ với sếp của mình về việc nên sử dụng append hay +. Vì họ đang sử dụng Append (Tôi vẫn không thể hiểu được như họ nói mỗi khi một đối tượng mới được tạo ra). Vì vậy, tôi đã nghĩ sẽ thực hiện một số nghiên cứu và phát triển. Mặc dù tôi yêu thích lời giải thích của Michael Borgwardt nhưng chỉ muốn đưa ra một lời giải thích nếu ai đó thực sự cần biết trong tương lai.

/**
 *
 * @author Perilbrain
 */
public class Appc {
    public Appc() {
        String x = "no name";
        x += "I have Added a name" + "We May need few more names" + Appc.this;
        x.concat(x);
        // x+=x.toString(); --It creates new StringBuilder object before concatenation so avoid if possible
        //System.out.println(x);
    }

    public void Sb() {
        StringBuilder sbb = new StringBuilder("no name");
        sbb.append("I have Added a name");
        sbb.append("We May need few more names");
        sbb.append(Appc.this);
        sbb.append(sbb.toString());
        // System.out.println(sbb.toString());
    }
}

và tháo gỡ các lớp trên đi ra như

 .method public <init>()V //public Appc()
  .limit stack 2
  .limit locals 2
met001_begin:                                  ; DATA XREF: met001_slot000i
  .line 12
    aload_0 ; met001_slot000
    invokespecial java/lang/Object.<init>()V
  .line 13
    ldc "no name"
    astore_1 ; met001_slot001
  .line 14

met001_7:                                      ; DATA XREF: met001_slot001i
    new java/lang/StringBuilder //1st object of SB
    dup
    invokespecial java/lang/StringBuilder.<init>()V
    aload_1 ; met001_slot001
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    ldc "I have Added a nameWe May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    aload_0 ; met001_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    astore_1 ; met001_slot001
  .line 15
    aload_1 ; met001_slot001
    aload_1 ; met001_slot001
    invokevirtual java/lang/String.concat(Ljava/lang/String;)Ljava/lang/Strin\
g;
    pop
  .line 18
    return //no more SB created
met001_end:                                    ; DATA XREF: met001_slot000i ...

; ===========================================================================

;met001_slot000                                ; DATA XREF: <init>r ...
    .var 0 is this LAppc; from met001_begin to met001_end
;met001_slot001                                ; DATA XREF: <init>+6w ...
    .var 1 is x Ljava/lang/String; from met001_7 to met001_end
  .end method
;44-1=44
; ---------------------------------------------------------------------------


; Segment type: Pure code
  .method public Sb()V //public void Sb
  .limit stack 3
  .limit locals 2
met002_begin:                                  ; DATA XREF: met002_slot000i
  .line 21
    new java/lang/StringBuilder
    dup
    ldc "no name"
    invokespecial java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    astore_1 ; met002_slot001
  .line 22

met002_10:                                     ; DATA XREF: met002_slot001i
    aload_1 ; met002_slot001
    ldc "I have Added a name"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 23
    aload_1 ; met002_slot001
    ldc "We May need few more names"
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 24
    aload_1 ; met002_slot001
    aload_0 ; met002_slot000
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/Object;)Ljava/lan\
g/StringBuilder;
    pop
  .line 25
    aload_1 ; met002_slot001
    aload_1 ; met002_slot001
    invokevirtual java/lang/StringBuilder.toString()Ljava/lang/String;
    invokevirtual java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lan\
g/StringBuilder;
    pop
  .line 28
    return
met002_end:                                    ; DATA XREF: met002_slot000i ...


;met002_slot000                                ; DATA XREF: Sb+25r
    .var 0 is this LAppc; from met002_begin to met002_end
;met002_slot001                                ; DATA XREF: Sb+9w ...
    .var 1 is sbb Ljava/lang/StringBuilder; from met002_10 to met002_end
  .end method
;96-49=48
; ---------------------------------------------------------------------------

Từ hai mã trên, bạn có thể thấy Michael đúng. Trong mỗi trường hợp chỉ có một đối tượng SB được tạo.


27

Kể từ Java 1.5, việc ghép một dòng đơn giản với "+" và StringBuilder.append () tạo ra chính xác cùng mã byte.

Vì vậy, để dễ đọc mã, hãy sử dụng "+".

2 trường hợp ngoại lệ:

  • môi trường đa luồng: StringBuffer
  • nối trong các vòng lặp: StringBuilder / StringBuffer

22

Sử dụng phiên bản mới nhất của Java (1.8), quá trình tháo gỡ ( javap -c) hiển thị tối ưu hóa được giới thiệu bởi trình biên dịch. +cũng sb.append()sẽ tạo ra mã rất giống nhau. Tuy nhiên, sẽ đáng để kiểm tra hành vi nếu chúng ta đang sử dụng +trong một vòng lặp for.

Thêm chuỗi bằng + trong vòng lặp for

Java:

public String myCatPlus(String[] vals) {
    String result = "";
    for (String val : vals) {
        result = result + val;
    }
    return result;
}

ByteCode :( fortrích đoạn vòng lặp)

12: iload         5
14: iload         4
16: if_icmpge     51
19: aload_3
20: iload         5
22: aaload
23: astore        6
25: new           #3                  // class java/lang/StringBuilder
28: dup
29: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
32: aload_2
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload         6
38: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
44: astore_2
45: iinc          5, 1
48: goto          12

Thêm chuỗi bằng Stringbuilder.append

Java:

public String myCatSb(String[] vals) {
    StringBuilder sb = new StringBuilder();
    for(String val : vals) {
        sb.append(val);
    }
    return sb.toString();
}

ByteCdoe :( fortrích đoạn vòng lặp)

17: iload         5
19: iload         4
21: if_icmpge     43
24: aload_3
25: iload         5
27: aaload
28: astore        6
30: aload_2
31: aload         6
33: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: pop
37: iinc          5, 1
40: goto          17
43: aload_2

Có một chút khác biệt rõ ràng mặc dù. Trong trường hợp đầu tiên, nơi +được sử dụng, mới StringBuilderđược tạo cho mỗi lần lặp vòng lặp và kết quả được tạo được lưu trữ bằng cách thực hiện toString()cuộc gọi (29 đến 41). Vì vậy, bạn đang tạo các Chuỗi trung gian mà bạn thực sự không cần trong khi sử dụng +toán tử trong forvòng lặp.


1
Đây là Oracle JDK hay OpenJDK?
Barshe Roussy

12

Trong Java 9, phiên bản 1 phải nhanh hơn vì nó được chuyển đổi thành invokedynamiccuộc gọi. Thông tin chi tiết có thể được tìm thấy trong JEP-280 :

Ý tưởng là thay thế toàn bộ điệu nhảy nối thêm StringBuilder bằng một lệnh gọi đơn giản được gọi tới java.lang.invoke.StringConcatFactory, sẽ chấp nhận các giá trị trong nhu cầu ghép nối.


9

Vì lý do hiệu suất, việc sử dụng +=( Stringnối) không được khuyến khích. Lý do là: Java Stringlà một bất biến, mỗi khi tạo ra một phép nối Stringmới, một cái mới có một dấu vân tay khác với cái cũ hơn đã có trong nhóm String ). Tạo các chuỗi mới gây áp lực lên GC và làm chậm chương trình: việc tạo đối tượng rất tốn kém.

Mã dưới đây sẽ làm cho nó thực tế hơn và rõ ràng cùng một lúc.

public static void main(String[] args) 
{
    // warming up
    for(int i = 0; i < 100; i++)
        RandomStringUtils.randomAlphanumeric(1024);
    final StringBuilder appender = new StringBuilder();
    for(int i = 0; i < 100; i++)
        appender.append(RandomStringUtils.randomAlphanumeric(i));

    // testing
    for(int i = 1; i <= 10000; i*=10)
        test(i);
}

public static void test(final int howMany) 
{
    List<String> samples = new ArrayList<>(howMany);
    for(int i = 0; i < howMany; i++)
        samples.add(RandomStringUtils.randomAlphabetic(128));

    final StringBuilder builder = new StringBuilder();
    long start = System.nanoTime();
    for(String sample: samples)
        builder.append(sample);
    builder.toString();
    long elapsed = System.nanoTime() - start;
    System.out.printf("builder - %d - elapsed: %dus\n", howMany, elapsed / 1000);

    String accumulator = "";
    start = System.nanoTime();
    for(String sample: samples)
        accumulator += sample;
    elapsed = System.nanoTime() - start;
    System.out.printf("concatenation - %d - elapsed: %dus\n", howMany, elapsed / (int) 1e3);

    start = System.nanoTime();
    String newOne = null;
    for(String sample: samples)
        newOne = new String(sample);
    elapsed = System.nanoTime() - start;
    System.out.printf("creation - %d - elapsed: %dus\n\n", howMany, elapsed / 1000);
}

Kết quả cho một lần chạy được báo cáo dưới đây.

builder - 1 - elapsed: 132us
concatenation - 1 - elapsed: 4us
creation - 1 - elapsed: 5us

builder - 10 - elapsed: 9us
concatenation - 10 - elapsed: 26us
creation - 10 - elapsed: 5us

builder - 100 - elapsed: 77us
concatenation - 100 - elapsed: 1669us
creation - 100 - elapsed: 43us

builder - 1000 - elapsed: 511us
concatenation - 1000 - elapsed: 111504us
creation - 1000 - elapsed: 282us

builder - 10000 - elapsed: 3364us 
concatenation - 10000 - elapsed: 5709793us
creation - 10000 - elapsed: 972us

Không xem xét kết quả cho 1 lần ghép (JIT vẫn chưa thực hiện công việc của mình), ngay cả đối với 10 lần ghép, hình phạt hiệu suất có liên quan; đối với hàng ngàn kết nối, sự khác biệt là rất lớn.

Bài học rút ra từ thí nghiệm rất nhanh này (có thể tái tạo dễ dàng với đoạn mã trên): không bao giờ sử dụng các +=chuỗi để nối các chuỗi với nhau, ngay cả trong những trường hợp rất cơ bản khi cần một vài phép nối (như đã nói, việc tạo chuỗi mới rất tốn kém và gây áp lực lên GC).


9

Xem ví dụ dưới đây:

static final int MAX_ITERATIONS = 50000;
static final int CALC_AVG_EVERY = 10000;

public static void main(String[] args) {
    printBytecodeVersion();
    printJavaVersion();
    case1();//str.concat
    case2();//+=
    case3();//StringBuilder
}

static void case1() {
    System.out.println("[str1.concat(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str = str.concat(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case2() {
    System.out.println("[str1+=str2]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    String str = "";
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str += UUID.randomUUID() + "---";
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");
}

static void case3() {
    System.out.println("[str1.append(str2)]");
    List<Long> savedTimes = new ArrayList();
    long startTimeAll = System.currentTimeMillis();
    StringBuilder str = new StringBuilder("");
    for (int i = 0; i < MAX_ITERATIONS; i++) {
        long startTime = System.currentTimeMillis();
        str.append(UUID.randomUUID() + "---");
        saveTime(savedTimes, startTime);
    }
    System.out.println("Created string of length:" + str.length() + " in " + (System.currentTimeMillis() - startTimeAll) + " ms");

}

static void saveTime(List<Long> executionTimes, long startTime) {
    executionTimes.add(System.currentTimeMillis() - startTime);
    if (executionTimes.size() % CALC_AVG_EVERY == 0) {
        out.println("average time for " + executionTimes.size() + " concatenations: "
                + NumberFormat.getInstance().format(executionTimes.stream().mapToLong(Long::longValue).average().orElseGet(() -> 0))
                + " ms avg");
        executionTimes.clear();
    }
}

Đầu ra:

phiên bản java bytecode: 8
java.version: 1.8.0_144
[str1.concat (str2)]
Thời gian trung bình cho 10000 concatenations: 0,096 ms AVG
thời gian trung bình cho 10000 concatenations: 0,185 ms AVG
thời gian trung bình cho 10000 concatenations: 0,327 ms AVG
thời gian trung bình cho 10000 concatenations: 0,501 ms AVG
thời gian trung bình cho 10000 concatenations: 0,656 ms AVG
tạo chuỗi có độ dài: 1.950.000 trong 17.745 ms
[str1 + = str2]
thời gian trung bình cho 10000 concatenations: 0,21 ms AVG
thời gian trung bình cho 10000 concatenations: 0,652 ms AVG
thời gian trung bình cho 10000 concatenations: 1.129 ms avg
thời gian trung bình cho 10000 concatenations: 1.727 ms avg
thời gian trung bình cho 10000 lần ghép: 2.302 ms avg
Chuỗi thời lượng được tạo: 1950000 trong 60279 ms
[str1.append (str2)]
thời gian trung bình cho 10000 lần ghép: 0,002 ms avg
thời gian trung bình cho 10000 lần nối: 0,002 ms
thời gian trung bình cho 10000 lần nối: 0,002 ms avg
thời gian trung bình cho 10000 concatenations: 0,002 ms avg
thời gian trung bình cho 10000 concatenations: 0,002 ms avg
Tạo chuỗi có độ dài: 1950000 trong 100 ms

Khi chiều dài chuỗi tăng, thời gian nối cũng vậy.
Đó là nơi StringBuilderchắc chắn là cần thiết.
Như bạn thấy, sự kết hợp : UUID.randomUUID()+"---", không thực sự ảnh hưởng đến thời gian.

Tái bút: Tôi không nghĩ Khi nào sử dụng StringBuilder trong Java thực sự là một bản sao của điều này.
Câu hỏi này nói về toString()điều mà hầu hết thời gian không thực hiện nối các chuỗi lớn.


Cập nhật 2019

Kể từ java8thời gian, mọi thứ đã thay đổi một chút. Có vẻ như bây giờ (java13), thời gian nối của +=thực tế giống như str.concat(). Tuy nhiên StringBuilderthời gian nối vẫn không đổi . (Bài viết gốc ở trên đã được chỉnh sửa một chút để thêm đầu ra dài dòng hơn)

phiên bản java bytecode: 13
java.version: 13.0.1
[str1.concat (str2)]
Thời gian trung bình cho 10000 concatenations: 0,047 ms AVG
thời gian trung bình cho 10000 concatenations: 0,1 ms AVG
thời gian trung bình cho 10000 concatenations: 0,17 ms AVG
thời gian trung bình cho 10000 concatenations: 0,255 ms AVG
thời gian trung bình cho 10000 concatenations: 0,336 ms AVG
tạo chuỗi có độ dài: 1.950.000 trong 9147 ms
[str1 + = str2]
thời gian trung bình cho 10000 concatenations: 0,037 ms AVG
thời gian trung bình cho 10000 concatenations: 0,097 ms AVG
thời gian trung bình cho 10000 concatenations: 0.249 ms avg
thời gian trung bình cho 10000 concatenations: 0.298 ms avg
thời gian trung bình cho 10000 lần ghép: 0,336 ms avg
Chuỗi thời lượng được tạo: 1950000 trong 10191 ms
[str1.append (str2)]
thời gian trung bình cho 10000 lần ghép: 0,001 ms avg
thời gian trung bình cho 10000 lần ghép: 0,001 ms avg
thời gian trung bình cho 10000 lần nối: 0,001 ms avg
thời gian trung bình cho 10000 concatenations: 0,001 ms avg
thời gian trung bình cho 10000 concatenations: 0,001 ms avg
Tạo chuỗi có độ dài: 1950000 trong 43 ms

bytecode:8/java.version:13Sự kết hợp đáng chú ý cũng có lợi ích hiệu suất tốt so vớibytecode:8/java.version:8


Đây phải là câu trả lời được chấp nhận .. nó phụ thuộc vào kích thước của Chuỗi luồng xác định lựa chọn concat hoặc StringBuilder
user1428716

@ user1428716 FYI: câu trả lời được cập nhật với kết quả java13mà giờ đã khác. Nhưng tôi nghĩ kết luận chính vẫn như cũ.
Marinos An

7

Apache Commons-Lang có lớp ToStringBuilder cực kỳ dễ sử dụng. Nó thực hiện tốt cả việc xử lý logic nối thêm cũng như định dạng về cách bạn muốn toString của mình trông như thế nào.

public void toString() {
     ToStringBuilder tsb =  new ToStringBuilder(this);
     tsb.append("a", a);
     tsb.append("b", b)
     return tsb.toString();
}

Sẽ trả lại đầu ra trông như thế com.blah.YourClass@abc1321f[a=whatever, b=foo].

Hoặc ở dạng cô đặc hơn bằng cách sử dụng chuỗi:

public void toString() {
     return new ToStringBuilder(this).append("a", a).append("b", b").toString();
}

Hoặc nếu bạn muốn sử dụng sự phản chiếu để bao gồm mọi trường của lớp:

public String toString() {
    return ToStringBuilder.reflectionToString(this);
}

Bạn cũng có thể tùy chỉnh kiểu của ToString nếu muốn.


4

Làm cho phương thức toString dễ đọc nhất có thể!

Ngoại lệ duy nhất cho điều này trong cuốn sách của tôi là nếu bạn có thể chứng minh với tôi rằng nó tiêu tốn tài nguyên đáng kể :) (Vâng, điều này có nghĩa là hồ sơ)

Cũng lưu ý rằng trình biên dịch Java 5 tạo mã nhanh hơn so với cách tiếp cận "StringBuffer" viết tay được sử dụng trong các phiên bản trước của Java. Nếu bạn sử dụng "+" thì cải tiến này và tương lai sẽ miễn phí.


3

Dường như có một số tranh luận về việc sử dụng StringBuilder có còn cần thiết với các trình biên dịch hiện tại hay không. Vì vậy, tôi nghĩ rằng tôi sẽ cho tôi 2 xu kinh nghiệm.

Tôi có một JDBCbộ kết quả gồm 10k hồ sơ (vâng, tôi cần tất cả chúng trong một đợt.) Sử dụng toán tử + mất khoảng 5 phút trên máy của tôi Java 1.8. Việc sử dụng stringBuilder.append("")mất ít hơn một giây cho cùng một truy vấn.

Vì vậy, sự khác biệt là rất lớn. Bên trong một vòng lặp StringBuildernhanh hơn nhiều.


2
Tôi nghĩ rằng cuộc tranh luận là về việc sử dụng nó bên ngoài các vòng lặp. Tôi nghĩ rằng có một sự đồng thuận bạn cần sử dụng nó trong một vòng lặp.
Jordan

2

Hiệu suất nối chuỗi thông minh bằng cách sử dụng '+' là tốn kém hơn vì nó phải tạo một bản sao hoàn toàn mới của Chuỗi vì Chuỗi là bất biến trong java. Điều này đóng vai trò đặc biệt nếu việc ghép nối rất thường xuyên, ví dụ: bên trong một vòng lặp. Sau đây là những gì IDEA của tôi gợi ý khi tôi cố gắng làm một việc như vậy:

nhập mô tả hình ảnh ở đây

Quy tắc chung:

  • Trong một phép gán chuỗi đơn, sử dụng nối chuỗi là tốt.
  • Nếu bạn đang lặp để xây dựng một khối lớn dữ liệu ký tự, hãy truy cập StringBuffer.
  • Sử dụng + = trên Chuỗi luôn luôn kém hiệu quả hơn so với sử dụng StringBuffer, do đó, nó sẽ rung chuông cảnh báo - nhưng trong một số trường hợp, tối ưu hóa đạt được sẽ không đáng kể so với các vấn đề dễ đọc, vì vậy hãy sử dụng ý thức chung của bạn.

Đây là một blog Jon Skeet đẹp xung quanh chủ đề này.


2
Bạn không bao giờ nên sử dụng StringBuffer trừ khi bạn hoàn toàn yêu cầu truy cập được đồng bộ hóa từ nhiều luồng. Mặt khác, thích StringBuilder không được đồng bộ hóa và do đó có ít chi phí hơn.
Erki der Loony

1

Tôi có thể chỉ ra rằng nếu bạn định lặp lại một bộ sưu tập và sử dụng StringBuilder, bạn có thể muốn kiểm tra Apache Commons LangStringUtils.join () (theo các hương vị khác nhau) không?

Bất kể hiệu suất, nó sẽ giúp bạn tiết kiệm được việc phải tạo StringBuilders và cho các vòng lặp cho những gì có vẻ như là lần thứ một triệu .


1

Đây là những gì tôi đã kiểm tra trong Java8

  • Sử dụng nối chuỗi
  • Sử dụng StringBuilder

    long time1 = System.currentTimeMillis();
    usingStringConcatenation(100000);
    System.out.println("usingStringConcatenation " + (System.currentTimeMillis() - time1) + " ms");
    
    time1 = System.currentTimeMillis();
    usingStringBuilder(100000);
    System.out.println("usingStringBuilder " + (System.currentTimeMillis() - time1) + " ms");
    
    
    private static void usingStringBuilder(int n)
    {
        StringBuilder str = new StringBuilder();
        for(int i=0;i<n;i++)
            str.append("myBigString");    
    }
    
    private static void usingStringConcatenation(int n)
    {
        String str = "";
        for(int i=0;i<n;i++)
            str+="myBigString";
    }

Đó thực sự là một cơn ác mộng nếu bạn đang sử dụng nối chuỗi cho số lượng lớn chuỗi.

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

-1

Tôi nghĩ rằng chúng ta nên đi với phương pháp nối thêm StringBuilder. Lý do là :

  1. Chuỗi concatenate sẽ tạo một đối tượng chuỗi mới mỗi lần (Vì String là đối tượng bất biến), vì vậy nó sẽ tạo ra 3 đối tượng.

  2. Với Trình xây dựng chuỗi, chỉ có một đối tượng sẽ được tạo [StringBuilder có thể thay đổi] và chuỗi tiếp theo sẽ được thêm vào nó.


Tại sao câu trả lời này bị bỏ qua? docs.oracle.com/javase/8/docs/api/java/util/stream/... - Biên Đổi Giảm
user1428716

-4

Đối với các chuỗi đơn giản như thế tôi thích sử dụng

"string".concat("string").concat("string");

Theo thứ tự, tôi muốn nói rằng phương thức xây dựng chuỗi ưa thích là sử dụng StringBuilder, String # concat (), sau đó là toán tử + quá tải. StringBuilder là một sự gia tăng hiệu suất đáng kể khi làm việc với các chuỗi lớn giống như sử dụng toán tử + là hiệu suất giảm lớn (giảm theo cấp số nhân khi kích thước Chuỗi tăng). Một vấn đề khi sử dụng .concat () là nó có thể ném NullPulumExceptions.


9
Việc sử dụng concat () có thể hoạt động kém hơn '+' vì JLS cho phép '+' được chuyển đổi thành StringBuilder và rất có thể tất cả các JVM đều làm như vậy hoặc sử dụng một giải pháp thay thế hiệu quả hơn - điều tương tự không có khả năng đúng concat phải tạo và loại bỏ ít nhất một chuỗi trung gian hoàn chỉnh trong ví dụ của bạn.
Lawrence Dol
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.