StringBuilder เทียบกับการรวมสตริงเข้าใน toString () ใน Java


925

รับ 2 toString()การใช้งานด้านล่างซึ่งเป็นที่ต้องการ:

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

หรือ

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

?

ที่สำคัญกว่านั้นเนื่องจากเรามีเพียง 3 คุณสมบัติเท่านั้นมันอาจไม่สร้างความแตกต่าง แต่ ณ จุดใดที่คุณจะเปลี่ยนจาก+concat เป็น StringBuilder?


40
คุณจะเปลี่ยนไปใช้ StringBuilder ในจุดใด เมื่อมันมีผลต่อหน่วยความจำหรือประสิทธิภาพ หรือเมื่อมันอาจ หากคุณกำลังทำสิ่งนี้เพียงสองสามครั้งจริง ๆ ไม่ต้องกังวล แต่ถ้าคุณกำลังจะทำมันซ้ำแล้วซ้ำอีกคุณควรเห็นความแตกต่างที่วัดได้เมื่อใช้ StringBuilder
ewall

พารามิเตอร์ในพารามิเตอร์คืออะไร
Asif Mushtaq

2
@UnKnown 100 เป็นขนาดเริ่มต้นของ StringBuilder
ไม่ใช่ซีเควนตัว

@nonsequitor ดังนั้นจำนวนสูงสุดของอักขระคือ 100?
Asif Mushtaq

10
@ ไม่รู้จักไม่เพียงขนาดเริ่มต้นถ้าคุณรู้ขนาดโดยประมาณของสตริงที่คุณกำลังติดต่อคุณสามารถบอกได้StringBuilderว่าขนาดเท่าไรที่จะจัดสรรล่วงหน้ามิฉะนั้นจะเกิดขึ้นถ้ามันหมดพื้นที่ต้องมีขนาดเป็นสองเท่าโดยการสร้างchar[]อาร์เรย์ใหม่แล้วคัดลอกข้อมูลไป - ซึ่งมีราคาแพง คุณสามารถโกงโดยการกำหนดขนาดและจากนั้นไม่จำเป็นสำหรับการสร้างอาร์เรย์นี้ - ดังนั้นหากคุณคิดว่าสตริงของคุณจะมีความยาว ~ 100 ตัวอักษรคุณสามารถตั้งค่า StringBuilder ให้เป็นขนาดนั้นและจะไม่ต้องขยายภายใน
ไม่ใช่ผู้ตัดต่อ

คำตอบ:


964

เวอร์ชัน 1 เป็นที่นิยมมากกว่าเพราะสั้นกว่าและคอมไพเลอร์จะแปลงให้เป็นเวอร์ชั่น 2 - ไม่มีความแตกต่างด้านประสิทธิภาพใด ๆ

ที่สำคัญกว่านั้นเนื่องจากเรามีเพียง 3 คุณสมบัติเท่านั้นมันอาจไม่สร้างความแตกต่าง แต่ ณ จุดใดที่คุณเปลี่ยนจาก concat เป็น builder?

เมื่อถึงจุดที่คุณต่อเชื่อมวนเป็นวงกลม - โดยปกติแล้วเมื่อคอมไพเลอร์ไม่สามารถแทนที่StringBuilderตัวเองได้


19
นั่นเป็นความจริง แต่การอ้างอิงภาษายังระบุด้วยว่านี่เป็นตัวเลือก ที่จริงแล้วฉันเพิ่งทำการทดสอบอย่างง่ายกับ JRE 1.6.0_15 และฉันไม่เห็นการเพิ่มประสิทธิภาพของคอมไพเลอร์ในคลาสที่ถอดรหัสแล้ว
bruno conde

37
ฉันเพิ่งลองรหัสจากคำถาม (รวบรวมใน JDK 1.6.0_16) และพบการเพิ่มประสิทธิภาพตามที่คาดไว้ ฉันค่อนข้างมั่นใจว่าคอมไพเลอร์สมัยใหม่ทุกคนจะทำ
Michael Borgwardt

22
คุณถูก. ดูที่ bytecode ฉันเห็นการเพิ่มประสิทธิภาพ StringBuilder อย่างชัดเจน ฉันใช้เครื่องถอดรหัสและบางวิธีก็แปลงกลับเป็น concat +1
bruno conde

80
ไม่ได้ที่จะชนะม้าตาย แต่ถ้อยคำในสเปคคือ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.คำสำคัญมีการอาจ ระบุว่าสิ่งนี้เป็นทางเลือกอย่างเป็นทางการ (แม้ว่ามีการนำไปใช้มากที่สุด) เราจะไม่ป้องกันตนเองหรือไม่
ลูคัส

93
@Lucas: ไม่เราไม่ควรทำ หากคอมไพเลอร์ตัดสินใจที่จะไม่ทำการปรับให้เหมาะสมนั้นจะเป็นเพราะมันไม่คุ้มค่า ใน 99% ของกรณีคอมไพเลอร์รู้ดีกว่าว่าการปรับให้เหมาะสมนั้นคุ้มค่าอะไร แน่นอนสถานการณ์ของคุณอาจตกอยู่ในอีก 1% แต่สามารถตรวจสอบได้โดยการเปรียบเทียบ (ระวัง) เท่านั้น
sleske

255

กุญแจสำคัญคือไม่ว่าคุณจะเขียนเรียงต่อกันทั้งหมดในที่เดียวหรือสะสมในช่วงเวลา

สำหรับตัวอย่างที่คุณให้ไว้ไม่มีจุดในการใช้ StringBuilder อย่างชัดเจน (ดูรหัสที่คอมไพล์สำหรับเคสแรกของคุณ)

แต่ถ้าคุณกำลังสร้างสตริงเช่นภายในวงใช้ StringBuilder

หากต้องการชี้แจงสมมติว่า hugeArray มีสตริงหลายพันรายการรหัสดังนี้:

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

ใช้เวลาและความทรงจำสิ้นเปลืองเมื่อเปรียบเทียบกับ:

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

3
ใช่ StringBuilder ไม่จำเป็นต้องสร้างวัตถุ String ซ้ำแล้วซ้ำอีก
Olga

124
Dammit ฉันใช้ทั้ง 2 ฟังก์ชั่นเพื่อทดสอบสตริงขนาดใหญ่ที่ฉันทำงานอยู่ 6.51 นาทีเทียบกับ 11secs
1722791

1
โดยวิธีที่คุณสามารถใช้result += s;เช่นกัน (ในตัวอย่างแรก)
RAnders00

1
คำสั่งนี้จะถูกสร้างขึ้นกี่วัตถุ? "{a:"+ a + ", b:" + b + ", c: " + c +"}";
ราวกับว่า Mushtaq

1
เกี่ยวกับสิ่งที่ชอบ: String str = (a == null) null: a '+ (b == null)? null: b '+ (c == null)? c: c '+ ... ; ? สิ่งนี้จะป้องกันไม่ให้เกิดประสิทธิภาพสูงสุดหรือไม่
amitfr

75

ฉันชอบ:

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

... เพราะสั้นและอ่านง่าย

ฉันจะไม่ทำสิ่งนี้ให้ดีที่สุดเพื่อความเร็วถ้าคุณไม่ใช้มันในวงที่มีจำนวนการทำซ้ำที่สูงมากและวัดความแตกต่างของประสิทธิภาพ

ฉันยอมรับว่าหากคุณต้องแสดงผลพารามิเตอร์จำนวนมากแบบฟอร์มนี้อาจทำให้เกิดความสับสน (เช่นความคิดเห็นข้อใดข้อหนึ่ง) ในกรณีนี้ฉันจะเปลี่ยนเป็นรูปแบบที่อ่านง่ายขึ้น (อาจใช้ToStringBuilderของ apache-commons - นำมาจากคำตอบของ matt b) และละเว้นประสิทธิภาพอีกครั้ง


64
จริง ๆ แล้วมันยาวกว่าประกอบด้วยสัญลักษณ์เพิ่มเติมและมีตัวแปรข้อความตามลำดับ
Tom Hawtin - tackline

4
ดังนั้นคุณจะบอกว่ามันอ่านน้อยกว่าหนึ่งในปัญหาอื่น ๆ ?
tangens

3
ฉันชอบเขียนสิ่งนี้เพราะมันง่ายกว่าที่จะเพิ่มตัวแปรเพิ่มเติม แต่ฉันไม่แน่ใจว่าจะอ่านได้ง่ายขึ้น - โดยเฉพาะอย่างยิ่งเมื่อจำนวนอาร์กิวเมนต์มีมากขึ้น นอกจากนี้ยังใช้งานไม่ได้เมื่อคุณต้องการเพิ่มบิตในเวลาต่างกัน
Alex Feinman

78
อ่านยากกว่า (สำหรับฉัน) ตอนนี้ฉันต้องสแกนไปมาระหว่าง {... } และพารามิเตอร์
Steve Kuo

10
ฉันชอบแบบฟอร์มนี้เพราะจะปลอดภัยหากหนึ่งในพารามิเตอร์คือnull
rds

74

ในกรณีส่วนใหญ่คุณจะไม่เห็นความแตกต่างที่แท้จริงระหว่างสองวิธี แต่มันง่ายที่จะสร้างสถานการณ์กรณีที่เลวร้ายที่สุดเช่นนี้

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+="*";
    }
}

ผลลัพธ์คือ:

slow elapsed 11741 ms
fast elapsed 7 ms

ปัญหาคือว่า + = ผนวกเข้ากับสตริงสร้างสายอักขระใหม่ดังนั้นค่าใช้จ่ายบางอย่างเป็นเชิงเส้นตามความยาวของสตริงของคุณ (ผลรวมของทั้งคู่)

ดังนั้น - สำหรับคำถามของคุณ:

วิธีที่สองจะเร็วกว่า แต่ก็อ่านได้น้อยกว่าและยากกว่าในการรักษา ดังที่ฉันได้กล่าวไว้ในกรณีเฉพาะของคุณคุณอาจไม่เห็นความแตกต่าง


อย่าลืมเกี่ยวกับ. catcat () ฉันจะคาดการณ์เวลาที่ผ่านไปให้อยู่ที่ใดก็ได้ตั้งแต่ 10 ถึง 18 ms ทำให้ไม่สำคัญเมื่อใช้สตริงสั้น ๆ เช่นตัวอย่างโพสต์ต้นฉบับ
Droo

9
ในขณะที่คุณพูดถูก+=ตัวอย่างดั้งเดิมคือลำดับ+ที่คอมไพเลอร์แปลงเป็นstring.concatสายเดียว ผลลัพธ์ของคุณไม่ได้ใช้
Blindy

1
@Blindy & Droo: - คุณทั้งคู่ถูกต้องการใช้ .concate ในสถานการณ์เช่นนี้เป็นการแก้ปัญหาที่ดีที่สุดเนื่องจาก + = สร้างวัตถุใหม่ทุกครั้งที่รูทีนวนรอบดำเนินการ
อันตราย

3
คุณรู้ไหมว่า toString () ของเขาไม่ได้ถูกเรียกเป็นวนซ้ำ?
Omry Yadan

ฉันลองตัวอย่างนี้เพื่อทดสอบความเร็ว ดังนั้นผลลัพธ์ของฉันคือ: ผ่านไปช้า 29672 ms; ผ่านไปอย่างรวดเร็ว 15 ms ดังนั้นคำตอบจึงชัดเจน แต่ถ้าเป็น 100 การวนซ้ำ - เวลาเท่ากัน - 0 ms ถ้า 500 ซ้ำ - 16 ms และ 0 ms และอื่น ๆ
Ernestas Gruodis

28

ฉันยังมีการปะทะกับหัวหน้าของฉันเกี่ยวกับข้อเท็จจริงที่ว่าจะใช้ผนวกหรือ + ขณะที่พวกเขากำลังใช้ผนวก (ฉันยังคงคิดออกตามที่พวกเขาพูดทุกครั้งที่มีการสร้างวัตถุใหม่) ดังนั้นฉันจึงคิดที่จะทำ R & D. แม้ว่าฉันจะรักการอธิบายของ Michael Borgwardt แต่แค่ต้องการแสดงคำอธิบายว่าใครบางคนจะต้องรู้จริง ๆ ในอนาคต

/**
 *
 * @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());
    }
}

และการถอดแยกชิ้นส่วนของคลาสเหนือออกมาเป็น

 .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
; ---------------------------------------------------------------------------

จากสองรหัสด้านบนคุณจะเห็นว่า Michael นั้นถูกต้องในแต่ละกรณีจะมีการสร้างวัตถุ SB เพียงชิ้นเดียว


27

ตั้งแต่ Java 1.5 การต่อข้อมูลบรรทัดเดียวอย่างง่ายด้วย "+" และ StringBuilder.append () จะสร้าง bytecode เดียวกันทั้งหมด

ดังนั้นเพื่อความสะดวกในการอ่านโค้ดให้ใช้ "+"

2 ข้อยกเว้น:

  • สภาพแวดล้อมแบบมัลติเธรด: StringBuffer
  • การต่อข้อมูลในลูป: StringBuilder / StringBuffer

22

การใช้ Java เวอร์ชันล่าสุด 1.8 ถอดแยกชิ้นส่วน ( javap -c) แสดงการปรับให้เหมาะสมที่คอมไพเลอร์แนะนำ +เช่นกันsb.append()จะสร้างรหัสที่คล้ายกันมาก อย่างไรก็ตามมันจะคุ้มค่าในการตรวจสอบพฤติกรรมถ้าเราใช้+ในการวนรอบ

การเพิ่มสตริงโดยใช้ + ในห่วงสำหรับ

Java:

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

ByteCode :( ส่วนที่forตัดตอนมา)

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

การเพิ่มสตริงโดยใช้ stringbuilder.append

Java:

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

ByteCdoe :( ส่วนที่forตัดตอนมา)

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

มีความแตกต่างที่เห็นได้ชัดอยู่เล็กน้อย ในกรณีแรกที่+มีการใช้งานใหม่StringBuilderจะถูกสร้างขึ้นสำหรับแต่ละการวนซ้ำวนซ้ำและผลลัพธ์ที่สร้างจะถูกจัดเก็บโดยทำการtoString()โทร (29 ถึง 41) ดังนั้นคุณกำลังสร้าง Strings ระดับกลางซึ่งคุณไม่ต้องการใช้จริงขณะใช้+โอเปอเรเตอร์ในforลูป


1
นี่คือ Oracle JDK หรือ OpenJDK หรือไม่
Christophe Roussy

12

ใน Java 9 เวอร์ชัน 1 ควรเร็วกว่าเพราะถูกแปลงเป็นการinvokedynamicโทร รายละเอียดเพิ่มเติมสามารถพบได้ในJEP-280 :

ความคิดคือการแทนที่ทั้งหมด StringBuilder ผนวกเต้นรำด้วยการเรียกง่ายเรียกใช้ java.lang.invoke.StringConcatFactory ที่จะยอมรับค่าในความต้องการของการต่อกัน


9

เพื่อเหตุผลด้านประสิทธิภาพการใช้+=(การStringต่อข้อมูล) จะไม่สนับสนุน เหตุผลคือ: Java Stringนั้นไม่เปลี่ยนรูปแบบทุกครั้งที่มีการสร้าง concatenation ใหม่Stringขึ้นมาใหม่ (ตัวใหม่นั้นมีลายนิ้วมือที่แตกต่างจากรุ่นเก่าที่มีอยู่แล้วใน String Pool ) การสร้างสตริงใหม่สร้างแรงกดดันต่อ GC และทำให้โปรแกรมช้าลง: การสร้างวัตถุมีราคาแพง

โค้ดด้านล่างควรทำให้ใช้งานได้จริงและชัดเจนในเวลาเดียวกัน

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);
}

ผลลัพธ์ของการวิ่งมีการรายงานด้านล่าง

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

ไม่พิจารณาผลลัพธ์สำหรับการต่อข้อมูล 1 รายการ (JIT ยังไม่ได้ทำงาน) แม้จะมีการต่อกัน 10 ครั้งการปรับการแสดงนั้นมีความเกี่ยวข้อง สำหรับการต่อพันนับพันความแตกต่างนั้นยิ่งใหญ่

บทเรียนที่เรียนรู้จากการทดลองอย่างรวดเร็วนี้ (ทำซ้ำได้ง่ายด้วยโค้ดด้านบน): อย่าใช้การ+=ต่อสตริงเข้าด้วยกันแม้ในกรณีพื้นฐานมากที่จำเป็นต้องมีการต่อกันสองสามครั้ง (ดังที่กล่าวว่าการสร้างสตริงใหม่นั้นมีราคาแพง GC)


9

ดูตัวอย่างด้านล่าง:

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();
    }
}

เอาท์พุท:

รุ่น Java bytecode: 8
java.version: 1.8.0_144
[str1.concat (str2)]
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.096 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.185 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.327 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.501 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.656 มิลลิ AVG
สร้างสายยาว: 1950000 ใน17745 มิลลิวินาที
[str1 + = str2]
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.21 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.652 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ การต่อข้อมูล 10,000 รายการ: 1.129 ms เฉลี่ย
เวลาเฉลี่ยสำหรับการต่อข้อมูล 10,000 ครั้ง: 1.727 มิลลิวินาที
เวลาเฉลี่ยสำหรับการต่อข้อมูล 10,000 ครั้ง: 2.302 ms avg
สร้างสตริงความยาว: 1950000 ใน60279 ms
[str1.append (str2)]
เวลาเฉลี่ยสำหรับการต่อกัน 10,000 ครั้ง: 0.002 ms เวลาเฉลี่ย
เฉลี่ยสำหรับ concatenations 10,000: 0.002 ms avg
เวลาเฉลี่ยสำหรับการต่อกัน 10,000: เวลาเฉลี่ย 0.002 ms
โดยเฉลี่ยสำหรับการต่อข้อมูล 10,000 ครั้ง: 0.002 มิลลิวินาทีเฉลี่ย
เวลาเฉลี่ยในการเชื่อมต่อ 10000 ครั้ง: 0.002 มิลลิวินาทีต่อนาที AVG
สร้างสตริงที่มีความยาว: 1950000 ใน100 ms

เมื่อความยาวของสตริงเพิ่มขึ้นดังนั้นเวลาในการต่อข้อมูล
นั่นคือStringBuilderสิ่งที่จำเป็นอย่างแน่นอน
ตามที่คุณเห็นการต่อข้อมูล: UUID.randomUUID()+"---"ไม่มีผลต่อเวลาจริงๆ

PS:ฉันไม่คิดว่าเมื่อใดที่จะใช้ StringBuilder ใน Javaเป็นสิ่งที่ซ้ำกันจริงๆ
คำถามนี้พูดถึงเกี่ยวกับเวลาtoString()ส่วนใหญ่ที่ไม่ทำการเรียงต่อกันของสตริงขนาดใหญ่


อัพเดต 2019

ตั้งแต่java8ครั้งสิ่งที่มีการเปลี่ยนแปลงเล็กน้อย ดูเหมือนว่าตอนนี้ (java13) เวลากำหนดการเป็นจริงเช่นเดียวกับ+= str.concat()อย่างไรก็ตามเวลาที่เรียงต่อกันยังคงเป็นอย่างต่อเนื่องStringBuilder (โพสต์ต้นฉบับด้านบนถูกแก้ไขเล็กน้อยเพื่อเพิ่มผลลัพธ์ verbose เพิ่มเติม)

รุ่น Java bytecode: 13
java.version: 13.0.1
[str1.concat (str2)]
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.047 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.1 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ 10000 concatenations: 0.17 มิลลิวินาทีโดยเฉลี่ยอาทิตย์
เวลาเฉลี่ยสำหรับ การต่อข้อมูล 10,000 ครั้ง: 0.255 มิลลิวินาทีเฉลี่ย
เวลาเฉลี่ยของการเชื่อมต่อ 10,000 ครั้ง: 0.336 มิลลิวินาทีต่อนาทีการ
สร้างสตริงของความยาว: 1950000 ใน9147 มิลลิวินาที
[str1 + = str2]
เวลาเฉลี่ยในการเชื่อมต่อ 10,000
ครั้ง: 0.097 มิลลิวินาที
โดยเฉลี่ย การต่อข้อมูล 10,000 ชุด: 0.249 ms เฉลี่ย
เวลาเฉลี่ยสำหรับการต่อข้อมูล 10,000 ชุด: 0.298 มิลลิวินาที
เวลาเฉลี่ยสำหรับการต่อข้อมูล 10,000 ครั้ง: 0.326 ms avg
สร้างสตริงของความยาว: 1950000 ใน10191 ms
[str1.append (str2)]
เวลาเฉลี่ยสำหรับการต่อกัน 10,000 ครั้ง: 0.001 มิลลิวินาทีเฉลี่ย
เวลาเฉลี่ยของการต่อกัน 10,000: 0.001 ms avg
เฉลี่ยสำหรับ 10,000 concatenations เวลาเฉลี่ย 0.001 ms avg
สำหรับการต่อข้อมูล 10,000 ครั้ง: 0.001 มิลลิวินาทีเฉลี่ย avg
เวลาการต่อเชื่อม 10,000 ครั้ง: 0.001 ms avg
สร้างสตริงความยาว: 1950000 ใน43 ms

น่าสังเกตว่าbytecode:8/java.version:13ชุดค่าผสมยังมีประโยชน์ด้านประสิทธิภาพที่ดีเมื่อเทียบกับbytecode:8/java.version:8


นี่ควรเป็นคำตอบที่ได้รับการยอมรับ .. มันขึ้นอยู่กับขนาดของ String Stream ที่กำหนดทางเลือกของ concat หรือ StringBuilder
user1428716

@ user1428716 FYI: คำตอบที่อัปเดตพร้อมกับผลลัพธ์java13ที่แตกต่างกันในขณะนี้ แต่ฉันคิดว่าข้อสรุปหลักยังคงเหมือนเดิม
Marinos

7

Apache Commons-Lang มีคลาสToStringBuilderซึ่งใช้งานง่ายสุด ๆ มันทำงานได้ดีทั้งในการจัดการภาคผนวก - ลอจิกรวมถึงการจัดรูปแบบวิธีที่คุณต้องการให้สายรัดของคุณดู

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

com.blah.YourClass@abc1321f[a=whatever, b=foo]จะกลับมาส่งออกที่มีลักษณะเหมือน

หรือในรูปแบบย่อโดยใช้การโยง:

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

หรือถ้าคุณต้องการใช้การสะท้อนเพื่อรวมทุกฟิลด์ของคลาส:

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

คุณยังสามารถปรับแต่งสไตล์ของ ToString ได้ตามต้องการ


4

สร้างเมธอด toString ให้อ่านง่ายที่สุด!

ข้อยกเว้นเพียงอย่างเดียวสำหรับสิ่งนี้ในหนังสือของฉันคือถ้าคุณสามารถพิสูจน์ให้ฉันเห็นว่ามันใช้ทรัพยากรจำนวนมาก :) (ใช่นี่หมายถึงการทำโปรไฟล์)

โปรดทราบว่าคอมไพเลอร์ Java 5 สร้างโค้ดได้เร็วกว่าวิธี "StringBuffer" ที่เขียนด้วยลายมือซึ่งใช้ใน Java เวอร์ชันก่อนหน้า หากคุณใช้ "+" สิ่งนี้และการปรับปรุงในอนาคตจะให้บริการฟรี


3

ดูเหมือนจะมีการถกเถียงกันอยู่บ้างว่าการใช้ StringBuilder นั้นยังจำเป็นต้องใช้กับคอมไพเลอร์ปัจจุบันหรือไม่ ดังนั้นฉันคิดว่าฉันจะให้ประสบการณ์ 2 เซ็นต์ของฉัน

ฉันมีJDBCชุดผลมาจากการบันทึก 10k (ใช่ฉันต้องการทั้งหมดของพวกเขาในหนึ่งชุด.) การใช้ประกอบการ + จะใช้เวลาประมาณ 5 Java 1.8นาทีในเครื่องของฉันด้วย การใช้stringBuilder.append("")เวลาน้อยกว่าหนึ่งวินาทีในการสืบค้นเดียวกัน

ดังนั้นความแตกต่างจึงมีขนาดใหญ่มาก ภายในวงStringBuilderนั้นเร็วกว่ามาก


2
ฉันคิดว่าการอภิปรายเกี่ยวกับการใช้มันนอกเหนือจากลูป ฉันคิดว่ามีฉันทามติที่คุณจำเป็นต้องใช้ภายในลูป
จอร์แดน

2

การเชื่อมสตริงอย่างชาญฉลาดด้วยการใช้ '+' นั้นมีค่าใช้จ่ายสูงกว่าเนื่องจากต้องทำการคัดลอก String ใหม่ทั้งหมดเนื่องจาก Strings นั้นไม่เปลี่ยนรูปในจาวา สิ่งนี้มีบทบาทเฉพาะหากการต่อข้อมูลเป็นสิ่งที่พบบ่อยมากเช่น: ภายในวง ต่อไปนี้เป็นสิ่งที่ IDEA ของฉันแนะนำเมื่อฉันพยายามทำสิ่งนั้น:

ป้อนคำอธิบายรูปภาพที่นี่

กฎทั่วไป:

  • ภายในการกำหนดค่าสตริงเดียวการใช้การต่อสตริงเป็นเรื่องปกติ
  • หากคุณวนลูปเพื่อสร้างกลุ่มข้อมูลอักขระขนาดใหญ่ให้ไปที่ StringBuffer
  • การใช้ + = บนสตริงจะมีประสิทธิภาพน้อยกว่าการใช้ StringBuffer ดังนั้นจึงควรส่งสัญญาณเตือนภัย - แต่ในบางกรณีการเพิ่มประสิทธิภาพที่ได้รับจะเล็กน้อยเมื่อเทียบกับปัญหาการอ่านดังนั้นใช้สามัญสำนึกของคุณ

นี่เป็นบล็อก Jon Skeet ที่ดีรอบ ๆ หัวข้อนี้


2
คุณไม่ควรใช้ StringBuffer เว้นแต่ว่าคุณต้องการการซิงโครไนซ์จากหลาย ๆ มิฉะนั้นจะต้องใช้ StringBuilder ซึ่งไม่ได้ทำการซิงโครไนซ์ดังนั้นจึงมีค่าใช้จ่ายน้อยลง
Erki der Loony

1

ฉันสามารถชี้ให้เห็นว่าถ้าคุณจะทำซ้ำมากกว่าชุดและใช้ StringBuilder คุณอาจต้องการตรวจสอบApache Commons LangและStringUtils.join ()ในรสชาติที่แตกต่างกัน?

โดยไม่คำนึงถึงประสิทธิภาพมันจะช่วยให้คุณไม่ต้องสร้าง StringBuilders และวนซ้ำสำหรับสิ่งที่ดูเหมือนเป็นล้านเวลา


1

นี่คือสิ่งที่ฉันตรวจสอบใน Java8

  • ใช้การต่อข้อมูลสตริง
  • ใช้ 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";
    }

มันเป็นฝันร้ายจริงๆถ้าคุณใช้การต่อสายสตริงกับสตริงจำนวนมาก

usingStringConcatenation 29321 ms
usingStringBuilder 2 ms

-1

ฉันคิดว่าเราควรไปด้วยวิธีผนวก StringBuilder เหตุผลที่ถูก:

  1. String Concatenate จะสร้างวัตถุสตริงใหม่ทุกครั้ง (เนื่องจาก String เป็นวัตถุที่ไม่เปลี่ยนรูป) ดังนั้นมันจะสร้างวัตถุ 3 รายการ

  2. ด้วยตัวสร้างสตริงวัตถุเดียวเท่านั้นที่จะสร้าง [StringBuilder ไม่แน่นอน] และสตริงเพิ่มเติมจะได้รับการผนวกเข้ากับมัน


ทำไมคำตอบนี้จึงลดลง? docs.oracle.com/javase/8/docs/api/java/util/stream/… - การลดการเปลี่ยนแปลงที่ไม่แน่นอน
1428716

-4

สำหรับสตริงแบบง่าย ๆ ที่ฉันชอบใช้

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

ตามลำดับฉันจะบอกว่าวิธีการสร้างสตริงที่ต้องการคือการใช้ StringBuilder, String # concat () จากนั้นตัวดำเนินการ + ที่โอเวอร์โหลด StringBuilder เป็นการเพิ่มประสิทธิภาพที่สำคัญเมื่อทำงานกับสตริงขนาดใหญ่เช่นเดียวกับการใช้ตัวดำเนินการ + เป็นการลดลงอย่างมากของประสิทธิภาพ ปัญหาอย่างหนึ่งของการใช้. catcat () คือมันสามารถใช้ NullPointerExceptions


9
การใช้ concat () มีแนวโน้มที่จะทำงานได้แย่กว่า '+' เนื่องจาก JLS อนุญาตให้ '+' ถูกแปลงเป็น StringBuilder และเป็นไปได้ว่า JVM ทั้งหมดจะทำเช่นนั้นหรือใช้ทางเลือกที่มีประสิทธิภาพมากขึ้น - สิ่งเดียวกันไม่น่าจะเป็นจริง concat ซึ่งจะต้องสร้างและยกเลิกอย่างน้อยหนึ่งสตริงกลางที่สมบูรณ์ในตัวอย่างของคุณ
Lawrence Dol
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.