การนำ StringBuilder มาใช้ซ้ำจะดีกว่าไหม


101

ฉันมีคำถามเกี่ยวกับประสิทธิภาพการใช้งาน StringBuilder ในลูปที่ยาวมากฉันกำลังจัดการ a StringBuilderและส่งต่อไปยังวิธีอื่นเช่นนี้:

for (loop condition) {
    StringBuilder sb = new StringBuilder();
    sb.append("some string");
    . . .
    sb.append(anotherString);
    . . .
    passToMethod(sb.toString());
}

การสร้างอินสแตนซ์StringBuilderในทุก ๆ วงจรเป็นทางออกที่ดีหรือไม่? และเรียกการลบแทนดีกว่าดังต่อไปนี้หรือไม่?

StringBuilder sb = new StringBuilder();
for (loop condition) {
    sb.delete(0, sb.length);
    sb.append("some string");
    . . .
    sb.append(anotherString);
    . . .
    passToMethod(sb.toString());
}

คำตอบ:


69

อันที่สองเร็วขึ้นประมาณ 25% ในเกณฑ์มาตรฐานขนาดเล็กของฉัน

public class ScratchPad {

    static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        for( int i = 0; i < 10000000; i++ ) {
            StringBuilder sb = new StringBuilder();
            sb.append( "someString" );
            sb.append( "someString2"+i );
            sb.append( "someStrin4g"+i );
            sb.append( "someStr5ing"+i );
            sb.append( "someSt7ring"+i );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for( int i = 0; i < 10000000; i++ ) {
            sb.delete( 0, sb.length() );
            sb.append( "someString" );
            sb.append( "someString2"+i );
            sb.append( "someStrin4g"+i );
            sb.append( "someStr5ing"+i );
            sb.append( "someSt7ring"+i );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
    }
}

ผล:

25265
17969

โปรดทราบว่านี่มาพร้อมกับ JRE 1.6.0_07


ตามแนวคิดของ Jon Skeet ในการแก้ไขนี่คือเวอร์ชัน 2 ผลลัพธ์เดียวกัน

public class ScratchPad {

    static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for( int i = 0; i < 10000000; i++ ) {
            sb.delete( 0, sb.length() );
            sb.append( "someString" );
            sb.append( "someString2" );
            sb.append( "someStrin4g" );
            sb.append( "someStr5ing" );
            sb.append( "someSt7ring" );
            a = sb.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
        time = System.currentTimeMillis();
        for( int i = 0; i < 10000000; i++ ) {
            StringBuilder sb2 = new StringBuilder();
            sb2.append( "someString" );
            sb2.append( "someString2" );
            sb2.append( "someStrin4g" );
            sb2.append( "someStr5ing" );
            sb2.append( "someSt7ring" );
            a = sb2.toString();
        }
        System.out.println( System.currentTimeMillis()-time );
    }
}

ผล:

5016
7516

4
ฉันได้เพิ่มการแก้ไขในคำตอบของฉันเพื่ออธิบายสาเหตุที่อาจเกิดขึ้น เดี๋ยวฉันจะดูอย่างรอบคอบมากขึ้น (45 นาที) โปรดทราบว่าการเรียงต่อกันในการเรียกต่อท้ายจะช่วยลดจุดของการใช้ StringBuilder ในตอนแรกได้บ้าง :)
Jon Skeet

3
นอกจากนี้ยังน่าสนใจที่จะเห็นว่าจะเกิดอะไรขึ้นหากคุณย้อนกลับสองช่วงตึก - JIT ยังคง "อุ่นเครื่อง" StringBuilder ในระหว่างการทดสอบครั้งแรก มันอาจจะไม่เกี่ยวข้อง แต่ก็น่าสนใจที่จะลอง
Jon Skeet

1
ฉันยังคงใช้เวอร์ชันแรกเพราะมันสะอาดกว่า แต่เป็นการดีที่คุณได้ทำเกณฑ์มาตรฐานแล้ว :) การเปลี่ยนแปลงที่แนะนำถัดไป: ลอง # 1 ด้วยความจุที่เหมาะสมที่ส่งไปยังตัวสร้าง
Jon Skeet

25
ใช้ sb.setLength (0); แทนที่จะเป็นวิธีที่เร็วที่สุดในการล้างเนื้อหาของ StringBuilder จากการสร้างวัตถุใหม่หรือใช้. ลบ () โปรดทราบว่าสิ่งนี้ใช้ไม่ได้กับ StringBuffer การตรวจสอบการทำงานพร้อมกันจะทำให้ข้อได้เปรียบด้านความเร็วเป็นโมฆะ
P Arrayah

1
คำตอบที่ไม่มีประสิทธิภาพ P Arrayah และ Dave Jarvis ถูกต้อง setLength (0) อยู่ไกลและเป็นคำตอบที่มีประสิทธิภาพมากที่สุด StringBuilder ได้รับการสนับสนุนโดยอาร์เรย์ถ่านและไม่แน่นอน เมื่อถึงจุดที่เรียกว่า. toString () อาร์เรย์ char จะถูกคัดลอกและใช้เพื่อสำรองสตริงที่ไม่เปลี่ยนรูป ณ จุดนี้บัฟเฟอร์ที่ไม่สามารถเปลี่ยนแปลงได้ของ StringBuilder สามารถใช้ซ้ำได้เพียงแค่เลื่อนตัวชี้การแทรกกลับไปที่ศูนย์ (ผ่าน. setLength (0)) sb.toString จะสร้างสำเนาอีกชุดหนึ่ง (อาร์เรย์ถ่านที่ไม่เปลี่ยนรูป) ดังนั้นการวนซ้ำแต่ละครั้งจึงต้องใช้บัฟเฟอร์สองตัวเมื่อเทียบกับเมธอด. setLength (0) ซึ่งต้องการบัฟเฟอร์ใหม่เพียงหนึ่งรายการต่อลูป
คริส

25

ในปรัชญาของการเขียน solid code การใส่ StringBuilder ไว้ในลูปของคุณจะดีกว่าเสมอ วิธีนี้จะไม่ออกนอกรหัสที่ตั้งใจไว้

ประการที่สองการปรับแต่งที่ใหญ่ที่สุดใน StringBuilder มาจากการให้ขนาดเริ่มต้นเพื่อหลีกเลี่ยงไม่ให้มันใหญ่ขึ้นในขณะที่ลูปทำงาน

for (loop condition) {
  StringBuilder sb = new StringBuilder(4096);
}

1
คุณสามารถกำหนดขอบเขตของสิ่งทั้งหมดได้ด้วยวงเล็บปีกกาโดยที่คุณไม่มี Stringbuilder อยู่ข้างนอก
Epaga

@Epaga: มันยังอยู่นอกวง ใช่มันไม่ก่อให้เกิดมลพิษขอบเขตด้านนอก แต่มันเป็นวิธีที่ผิดธรรมชาติที่จะเขียนรหัสสำหรับการปรับปรุงประสิทธิภาพการทำงานที่ยังไม่ได้รับการยืนยันในบริบท
Jon Skeet

หรือที่ดีกว่านั้นคือใส่ทุกอย่างด้วยวิธีการของมันเอง ;-) แต่ฉันได้ยินว่าคุณพูดถึงบริบท
Epaga

ยังดีกว่าเริ่มต้นด้วยขนาดที่คาดไว้แทนที่จะรวมหมายเลขตามอำเภอใจ (4096) รหัสของคุณอาจส่งคืนสตริงที่อ้างถึงถ่าน [] ขนาด 4096 (ขึ้นอยู่กับ JDK เท่าที่ฉันจำได้ว่าเป็นกรณีของ 1.4)
kohlerm

24

ยังเร็วกว่า:

public class ScratchPad {

    private static String a;

    public static void main( String[] args ) throws Exception {
        long time = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder( 128 );

        for( int i = 0; i < 10000000; i++ ) {
            // Resetting the string is faster than creating a new object.
            // Since this is a critical loop, every instruction counts.
            //
            sb.setLength( 0 );
            sb.append( "someString" );
            sb.append( "someString2" );
            sb.append( "someStrin4g" );
            sb.append( "someStr5ing" );
            sb.append( "someSt7ring" );
            setA( sb.toString() );
        }

        System.out.println( System.currentTimeMillis()-time );
    }

    private static void setA( String aString ) {
        a = aString;
    }
}

ในปรัชญาของการเขียน solid code ควรซ่อนผลงานภายในของวิธีการจากวัตถุที่ใช้วิธีนี้ ดังนั้นจึงไม่แตกต่างจากมุมมองของระบบไม่ว่าคุณจะประกาศ StringBuilder ใหม่ภายในลูปหรือภายนอกลูป เนื่องจากการประกาศนอกลูปนั้นเร็วกว่าและไม่ได้ทำให้โค้ดอ่านซับซ้อนมากขึ้นจากนั้นจึงใช้อ็อบเจกต์ซ้ำแทนการคืนสถานะ

แม้ว่าโค้ดจะซับซ้อนกว่านี้และคุณรู้แน่นอนว่าการสร้างอินสแตนซ์ของอ็อบเจ็กต์คือคอขวดโปรดแสดงความคิดเห็น

สามรันด้วยคำตอบนี้:

$ java ScratchPad
1567
$ java ScratchPad
1569
$ java ScratchPad
1570

สามรันด้วยคำตอบอื่น ๆ :

$ java ScratchPad2
1663
2231
$ java ScratchPad2
1656
2233
$ java ScratchPad2
1658
2242

แม้ว่าจะไม่สำคัญ แต่การตั้งค่าStringBuilderขนาดบัฟเฟอร์เริ่มต้นจะให้ผลตอบแทนเล็กน้อย


3
นี่เป็นคำตอบที่ดีที่สุด StringBuilder ได้รับการสนับสนุนโดยอาร์เรย์ถ่านและไม่แน่นอน เมื่อถึงจุดที่เรียกว่า. toString () อาร์เรย์ char จะถูกคัดลอกและใช้เพื่อสำรองสตริงที่ไม่เปลี่ยนรูป ณ จุดนี้บัฟเฟอร์ที่ไม่สามารถเปลี่ยนแปลงได้ของ StringBuilder สามารถใช้ซ้ำได้เพียงแค่เลื่อนตัวชี้การแทรกกลับไปที่ศูนย์ (ผ่าน. setLength (0)) คำตอบเหล่านั้นที่แนะนำการจัดสรร StringBuilder ใหม่เอี่ยมต่อลูปดูเหมือนจะไม่ทราบว่า. toString สร้างสำเนาขึ้นมาใหม่ดังนั้นการวนซ้ำแต่ละครั้งจึงต้องใช้บัฟเฟอร์สองตัวเมื่อเทียบกับเมธอด. setLength (0) ซึ่งต้องการบัฟเฟอร์ใหม่เพียงหนึ่งรายการต่อลูป
Chris

12

โอเคตอนนี้ฉันเข้าใจแล้วว่าเกิดอะไรขึ้นและมันก็สมเหตุสมผลแล้ว

ฉันรู้สึกประทับใจที่toStringเพิ่งส่งผ่านพื้นฐานchar[]ไปยังตัวสร้างสตริงซึ่งไม่ได้ถ่ายสำเนา จากนั้นสำเนาจะถูกสร้างขึ้นในการดำเนินการ "เขียน" ถัดไป (เช่นdelete) ฉันเชื่อว่านี่เป็นกรณีของStringBufferเวอร์ชันก่อนหน้านี้ (ไม่ใช่ตอนนี้) แต่ไม่ - toStringเพียงแค่ส่งอาร์เรย์ (และดัชนีและความยาว) ไปยังตัวStringสร้างสาธารณะซึ่งรับสำเนา

ดังนั้นในกรณี "ใช้ซ้ำStringBuilder" เราจึงสร้างสำเนาข้อมูลหนึ่งชุดต่อสตริงโดยใช้อาร์เรย์ถ่านเดียวกันในบัฟเฟอร์ตลอดเวลา เห็นได้ชัดว่าการสร้างใหม่StringBuilderทุกครั้งจะสร้างบัฟเฟอร์พื้นฐานใหม่ - จากนั้นบัฟเฟอร์นั้นจะถูกคัดลอก (ค่อนข้างไม่มีจุดหมายในกรณีเฉพาะของเรา แต่ทำด้วยเหตุผลด้านความปลอดภัย) เมื่อสร้างสตริงใหม่

ทั้งหมดนี้นำไปสู่เวอร์ชันที่สองมีประสิทธิภาพมากขึ้นอย่างแน่นอน - แต่ในขณะเดียวกันฉันก็ยังบอกว่ามันเป็นรหัสที่น่าเกลียดกว่า


แค่ข้อมูลตลก ๆ เกี่ยวกับ. NET สถานการณ์ก็แตกต่างออกไป .NET StringBuilder แก้ไขอ็อบเจ็กต์ "string" ปกติภายในและเมธอด toString เพียงแค่ส่งกลับ (ทำเครื่องหมายว่าไม่สามารถแก้ไขได้ดังนั้นการจัดการ StringBuilder ที่ตามมาจะสร้างขึ้นใหม่) ดังนั้นลำดับ "ใหม่ StringBuilder-> แก้ไขมัน -> เป็น String" โดยทั่วไปจะไม่สร้างสำเนาเพิ่มเติมใด ๆ (เฉพาะสำหรับการขยายพื้นที่เก็บข้อมูลหรือการย่อขนาดเท่านั้นหากความยาวสตริงที่ได้นั้นสั้นกว่าความจุมาก) ใน Java รอบนี้จะสร้างสำเนาอย่างน้อยหนึ่งชุดเสมอ (ใน StringBuilder.toString ())
Ivan Dubrov

Sun JDK pre-1.5 มีการปรับให้เหมาะสมอย่างที่คุณคาดไม่ถึง
Dan Berindei

9

เนื่องจากฉันไม่คิดว่าจะถูกชี้ให้เห็นเนื่องจากการเพิ่มประสิทธิภาพที่สร้างขึ้นในคอมไพเลอร์ Sun Java ซึ่งสร้าง StringBuilders โดยอัตโนมัติ (StringBuffers ก่อน J2SE 5.0) เมื่อเห็นการต่อสตริงตัวอย่างแรกในคำถามจะเทียบเท่ากับ:

for (loop condition) {
  String s = "some string";
  . . .
  s += anotherString;
  . . .
  passToMethod(s);
}

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

แต่ถ้าคุณประสบปัญหาเกี่ยวกับประสิทธิภาพจริงๆให้เพิ่มประสิทธิภาพให้ดีที่สุด ฉันจะเริ่มต้นด้วยการระบุขนาดบัฟเฟอร์ของ StringBuilder อย่างชัดเจนต่อ Jon Skeet


4

JVM ยุคใหม่ฉลาดมากในเรื่องแบบนี้ ฉันจะไม่เดาเป็นครั้งที่สองและทำบางสิ่งที่แฮ็กซึ่งสามารถบำรุงรักษา / อ่านได้น้อย ... เว้นแต่คุณจะทำเครื่องหมายมาตรฐานที่เหมาะสมกับข้อมูลการผลิตที่ตรวจสอบการปรับปรุงประสิทธิภาพที่ไม่สำคัญ (และจัดทำเป็นเอกสาร;)


โดยที่ "ไม่สำคัญ" คือกุญแจสำคัญ - เกณฑ์มาตรฐานสามารถแสดงให้เห็นว่ารูปแบบหนึ่งเร็วขึ้นตามสัดส่วนแต่ไม่มีคำใบ้ว่าจะใช้เวลาเท่าไรในแอปจริง :)
Jon Skeet

ดูเกณฑ์มาตรฐานในคำตอบของฉันด้านล่าง ทางที่สองเร็วกว่า
Epaga

1
@Epaga: เกณฑ์มาตรฐานของคุณบอกเพียงเล็กน้อยเกี่ยวกับการปรับปรุงประสิทธิภาพในแอปจริงซึ่งเวลาที่ใช้ในการจัดสรร StringBuilder อาจไม่สำคัญเมื่อเทียบกับส่วนที่เหลือของลูป นั่นเป็นเหตุผลว่าทำไมบริบทจึงมีความสำคัญในการเปรียบเทียบ
Jon Skeet

1
@Epaga: จนกว่าเขาจะวัดด้วยรหัสจริงของเขาเราก็ไม่รู้ว่ามันสำคัญแค่ไหน หากมีโค้ดจำนวนมากสำหรับการวนซ้ำแต่ละครั้งฉันสงสัยอย่างยิ่งว่ามันจะยังไม่เกี่ยวข้อง เราไม่รู้ว่ามีอะไรอยู่ใน "... "
Jon Skeet

1
(อย่าเข้าใจว่าฉันผิด btw - ผลการวัดประสิทธิภาพของคุณยังคงน่าสนใจอยู่ในตัวฉันเองฉันหลงใหลในไมโครเบนช์มาร์กฉันไม่ชอบการดัดโค้ดของฉันให้ผิดรูปก่อนทำการทดสอบในชีวิตจริงเช่นกัน)
Jon Skeet

4

จากประสบการณ์ของฉันกับการพัฒนาซอฟต์แวร์บน Windows ฉันจะบอกว่าการล้าง StringBuilder ออกระหว่างลูปของคุณมีประสิทธิภาพที่ดีกว่าการสร้างอินสแตนซ์ StringBuilder ด้วยการทำซ้ำแต่ละครั้ง การล้างจะช่วยให้สามารถเขียนทับหน่วยความจำนั้นได้ทันทีโดยไม่ต้องมีการจัดสรรเพิ่มเติม ฉันไม่คุ้นเคยกับตัวเก็บขยะ Java มากพอ แต่ฉันคิดว่าการว่างและไม่มีการจัดสรรใหม่ (เว้นแต่สตริงถัดไปของคุณจะเติบโต StringBuilder) มีประโยชน์มากกว่าการสร้างอินสแตนซ์

(ความเห็นของฉันตรงกันข้ามกับสิ่งที่คนอื่นแนะนำอืมได้เวลาเปรียบเทียบแล้ว)


สิ่งนี้ก็คือต้องมีการจัดสรรหน่วยความจำเพิ่มเติมอยู่ดีเนื่องจากข้อมูลที่มีอยู่จะถูกใช้โดย String ที่สร้างขึ้นใหม่ในตอนท้ายของการวนซ้ำแบบวนซ้ำก่อนหน้านี้
Jon Skeet

โอ้นั่นเป็นเหตุผลที่ฉันมีแม้ว่า toString นั้นกำลังจัดสรรและส่งคืนอินสแตนซ์สตริงใหม่และบัฟเฟอร์ไบต์สำหรับตัวสร้างกำลังล้างแทนที่จะจัดสรรใหม่
cfeduke

เกณฑ์มาตรฐานของ Epaga แสดงให้เห็นว่าการเคลียร์และการใช้ซ้ำคือการได้รับจากการสร้างอินสแตนซ์ในทุก ๆ รอบ
cfeduke

1

สาเหตุที่การทำ 'setLength' หรือ 'delete' ช่วยเพิ่มประสิทธิภาพนั้นส่วนใหญ่จะเป็นรหัส 'การเรียนรู้' ในขนาดที่เหมาะสมของบัฟเฟอร์และไม่ต้องทำการจัดสรรหน่วยความจำ โดยทั่วไปแล้วผมขอแนะนำให้ปล่อยให้คอมไพเลอร์ทำเพิ่มประสิทธิภาพสตริง อย่างไรก็ตามหากประสิทธิภาพมีความสำคัญฉันมักจะคำนวณขนาดที่คาดหวังของบัฟเฟอร์ไว้ล่วงหน้า ขนาด StringBuilder เริ่มต้นคือ 16 อักขระ หากคุณเติบโตเกินกว่านั้นก็ต้องปรับขนาด การปรับขนาดเป็นจุดที่ทำให้ประสิทธิภาพหายไป นี่คืออีกหนึ่งเกณฑ์มาตรฐานขนาดเล็กที่แสดงให้เห็นสิ่งนี้:

private void clear() throws Exception {
    long time = System.currentTimeMillis();
    int maxLength = 0;
    StringBuilder sb = new StringBuilder();

    for( int i = 0; i < 10000000; i++ ) {
        // Resetting the string is faster than creating a new object.
        // Since this is a critical loop, every instruction counts.
        //
        sb.setLength( 0 );
        sb.append( "someString" );
        sb.append( "someString2" ).append( i );
        sb.append( "someStrin4g" ).append( i );
        sb.append( "someStr5ing" ).append( i );
        sb.append( "someSt7ring" ).append( i );
        maxLength = Math.max(maxLength, sb.toString().length());
    }

    System.out.println(maxLength);
    System.out.println("Clear buffer: " + (System.currentTimeMillis()-time) );
}

private void preAllocate() throws Exception {
    long time = System.currentTimeMillis();
    int maxLength = 0;

    for( int i = 0; i < 10000000; i++ ) {
        StringBuilder sb = new StringBuilder(82);
        sb.append( "someString" );
        sb.append( "someString2" ).append( i );
        sb.append( "someStrin4g" ).append( i );
        sb.append( "someStr5ing" ).append( i );
        sb.append( "someSt7ring" ).append( i );
        maxLength = Math.max(maxLength, sb.toString().length());
    }

    System.out.println(maxLength);
    System.out.println("Pre allocate: " + (System.currentTimeMillis()-time) );
}

public void testBoth() throws Exception {
    for(int i = 0; i < 5; i++) {
        clear();
        preAllocate();
    }
}

ผลลัพธ์แสดงการนำวัตถุกลับมาใช้ใหม่เร็วกว่าการสร้างบัฟเฟอร์ตามขนาดที่คาดไว้ประมาณ 10%


1

ฮ่า ๆ เป็นครั้งแรกที่ฉันเคยเห็นคนเปรียบเทียบประสิทธิภาพโดยการรวมสตริงใน StringBuilder เพื่อจุดประสงค์นั้นถ้าคุณใช้ "+" อาจเร็วกว่านี้ก็ได้ D. วัตถุประสงค์ของการใช้ StringBuilder เพื่อเร่งความเร็วในการดึงสตริงทั้งหมดตามแนวคิดของ "ท้องที่"

ในสถานการณ์สมมติที่คุณดึงค่า String บ่อยๆซึ่งไม่จำเป็นต้องมีการเปลี่ยนแปลงบ่อย Stringbuilder อนุญาตให้มีการดึงข้อมูลสตริงที่มีประสิทธิภาพสูงขึ้น และนั่นคือจุดประสงค์ของการใช้ Stringbuilder .. กรุณาอย่าทดสอบ MIS - จุดประสงค์หลักของสิ่งนั้น ..

บางคนกล่าวว่าเครื่องบินบินเร็วขึ้น ดังนั้นฉันจึงทดสอบกับจักรยานของฉันและพบว่าเครื่องบินเคลื่อนที่ช้าลง คุณรู้ไหมว่าฉันตั้งค่าการทดสอบอย่างไร D


1

ไม่เร็วขึ้นอย่างมีนัยสำคัญ แต่จากการทดสอบของฉันแสดงให้เห็นว่าโดยเฉลี่ยเร็วขึ้นสองสามมิลลิวินาทีโดยใช้ 1.6.0_45 64 บิต: ใช้ StringBuilder.setLength (0) แทน StringBuilder.delete ():

time = System.currentTimeMillis();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < 10000000; i++) {
    sb2.append( "someString" );
    sb2.append( "someString2"+i );
    sb2.append( "someStrin4g"+i );
    sb2.append( "someStr5ing"+i );
    sb2.append( "someSt7ring"+i );
    a = sb2.toString();
    sb2.setLength(0);
}
System.out.println( System.currentTimeMillis()-time );

1

วิธีที่เร็วที่สุดคือใช้ "setLength" จะไม่เกี่ยวข้องกับการคัดลอก วิธีการสร้าง StringBuilder ใหม่ควรจะสมบูรณ์ออก ความช้าสำหรับ StringBuilder.delete (int start, int end) เป็นเพราะมันจะคัดลอกอาร์เรย์อีกครั้งสำหรับส่วนการปรับขนาด

 System.arraycopy(value, start+len, value, start, count-end);

หลังจากนั้น StringBuilder.delete () จะอัปเดต StringBuilder.count เป็นขนาดใหม่ ในขณะที่ StringBuilder.setLength () ทำให้การอัปเดตStringBuilder.countเป็นขนาดใหม่ง่ายขึ้น


0

อย่างแรกดีกว่าสำหรับมนุษย์ ถ้าอย่างที่สองเร็วกว่าเล็กน้อยในบางเวอร์ชันของ JVM บางรุ่นแล้วล่ะ?

หากประสิทธิภาพเป็นสิ่งสำคัญให้ข้าม StringBuilder และเขียนของคุณเอง หากคุณเป็นโปรแกรมเมอร์ที่ดีและพิจารณาว่าแอปของคุณใช้ฟังก์ชันนี้อย่างไรคุณควรจะทำให้เร็วขึ้นได้ คุ้มมั้ย? อาจจะไม่.

เหตุใดคำถามนี้จึงถูกจ้องว่าเป็น "คำถามโปรด" เนื่องจากการเพิ่มประสิทธิภาพเป็นเรื่องสนุกมากไม่ว่าจะใช้งานได้จริงหรือไม่ก็ตาม


ไม่ใช่คำถามเชิงวิชาการเท่านั้น ในขณะที่ส่วนใหญ่ (อ่าน 95%) ฉันชอบความสามารถในการอ่านและการบำรุงรักษา แต่มีหลายกรณีที่การปรับปรุงเล็กน้อยทำให้เกิดความแตกต่างอย่างมาก ...
Pier Luigi

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

0

ฉันไม่คิดว่าจะมีเหตุผลที่จะพยายามเพิ่มประสิทธิภาพเช่นนั้น วันนี้ (2019) สถิติทั้งสองกำลังทำงานประมาณ 11 วินาทีสำหรับ 100.000.000 ลูปบนแล็ปท็อป I5 ของฉัน:

    String a;
    StringBuilder sb = new StringBuilder();
    long time = 0;

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        StringBuilder sb3 = new StringBuilder();
        sb3.append("someString");
        sb3.append("someString2");
        sb3.append("someStrin4g");
        sb3.append("someStr5ing");
        sb3.append("someSt7ring");
        a = sb3.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        sb.setLength(0);
        sb.delete(0, sb.length());
        sb.append("someString");
        sb.append("someString2");
        sb.append("someStrin4g");
        sb.append("someStr5ing");
        sb.append("someSt7ring");
        a = sb.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

==> 11000 msec (การประกาศภายในลูป) และ 8236 msec (การประกาศนอกลูป)

แม้ว่าฉันจะใช้โปรแกรมสำหรับการลบที่อยู่โดยมีบางพันล้านลูปซึ่งต่างกัน 2 วินาที สำหรับ 100 ล้านลูปไม่ได้สร้างความแตกต่างใด ๆ เนื่องจากโปรแกรมนั้นทำงานเป็นเวลาหลายชั่วโมง โปรดทราบว่าสิ่งต่าง ๆ จะแตกต่างกันหากคุณมีคำสั่งต่อท้ายเพียงคำสั่งเดียว:

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        StringBuilder sb3 = new StringBuilder();
        sb3.append("someString");
            a = sb3.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        sb.setLength(0);
        sb.delete(0, sb.length());
        sb.append("someString");
        a = sb.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

==> 3416 msec (วงใน), 3555 msec (วงนอก) คำสั่งแรกที่สร้าง StringBuilder ภายในวงจะเร็วกว่าในกรณีนั้น และหากคุณเปลี่ยนลำดับการดำเนินการจะเร็วกว่ามาก:

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        sb.setLength(0);
        sb.delete(0, sb.length());
        sb.append("someString");
        a = sb.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

    System.gc();
    time = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
        StringBuilder sb3 = new StringBuilder();
        sb3.append("someString");
            a = sb3.toString();
    }
    System.out.println(System.currentTimeMillis() - time);

==> 3638 msec (วงนอก), 2908 msec (วงใน)

ขอแสดงความนับถือ Ulrich


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.