java - जावा में toString() में स्ट्रिंगबिल्डर बनाम स्ट्रिंग concatenation




performance stringbuilder (12)

नीचे दिए गए 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 गुण हैं, इससे कोई फर्क नहीं पड़ता है, लेकिन आप किस बिंदु पर + कॉन्सट से StringBuilder तक स्विच करेंगे?


'+' का उपयोग करके प्रदर्शनवार स्ट्रिंग कॉन्सटेनेशन महंगा है क्योंकि इसे स्ट्रिंग की पूरी नई प्रतिलिपि बनाना है क्योंकि स्ट्रिंग जावा में अपरिवर्तनीय हैं। यदि कॉन्सटेनेशन बहुत बार होता है, तो यह विशेष भूमिका निभाता है, उदाहरण के लिए: एक लूप के अंदर। जब मैं ऐसी चीज करने का प्रयास करता हूं तो मेरा आईडीईए सुझाव देता है:


अपाचे कॉमन्स-लैंग में एक ToStringBuilder क्लास है जो उपयोग करने में बहुत आसान है। यह परिशिष्ट-तर्क को संभालने और साथ ही स्वरूपण करने के लिए आपके स्ट्रिंग को देखना चाहते हैं, दोनों को स्वरूपित करने का एक अच्छा काम करता है।

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

[email protected][a=whatever, b=foo] जैसा दिखता है आउटपुट [email protected][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 की शैली को भी कस्टमाइज़ कर सकते हैं।


कुंजी यह है कि क्या आप एक ही स्थान पर एक ही संगतता लिख ​​रहे हैं या समय के साथ जमा कर रहे हैं।

आपके द्वारा दिए गए उदाहरण के लिए, स्ट्रिंगबिल्डर का स्पष्ट रूप से उपयोग करने में कोई बात नहीं है। (अपने पहले मामले के लिए संकलित कोड देखें।)

लेकिन यदि आप एक स्ट्रिंग बना रहे हैं जैसे लूप के अंदर, स्ट्रिंगबिल्डर का उपयोग करें।

स्पष्टीकरण के लिए, यह मानते हुए कि विशालअरे में हजारों तार हैं, इस तरह कोड:

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

बहुत समय है- और स्मृति-अपशिष्ट की तुलना में:

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

क्या मैं यह इंगित कर सकता हूं कि यदि आप संग्रह पर पुन: प्रयास करने जा रहे हैं और स्ट्रिंगबिल्डर का उपयोग कर रहे हैं, तो आप अपाचे कॉमन्स लैंग और StringUtils.join() (विभिन्न स्वादों में) को देखना चाहेंगे?

प्रदर्शन के बावजूद, यह आपको स्ट्रिंगबिल्डर बनाने और लाखों समय की तरह दिखने के लिए लूप के लिए सहेजने के लिए बचाएगा।


जावा 1.5 के बाद से, "+" और स्ट्रिंगबिल्डर.एपेंड () के साथ सरल एक पंक्ति संगतता बिल्कुल वही बाइटकोड उत्पन्न करती है।

तो कोड पठनीयता के लिए, "+" का उपयोग करें।

2 अपवाद:

  • बहुप्रचारित वातावरण: स्ट्रिंगबफर
  • लूप में concatenation: स्ट्रिंगबिल्डर / स्ट्रिंगबफर

जावा 9 में संस्करण 1 तेज होना चाहिए क्योंकि इसे invokedynamic कॉल में परिवर्तित किया गया है। JEP-280 में अधिक जानकारी मिल सकती है:

विचार है कि पूरे स्ट्रिंगबिल्डर एपेंड डांस को एक सरल इनवॉक्डेनेमिक कॉल के साथ java.lang.invoke.StringConcatFactory में बदलना है, जो समेकन की आवश्यकता में मूल्यों को स्वीकार करेगा।


ज्यादातर मामलों में, आपको दो दृष्टिकोणों के बीच वास्तविक अंतर दिखाई नहीं देगा, लेकिन इस तरह के सबसे खराब केस परिदृश्य को बनाना आसान है:

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

समस्या यह है कि + = स्ट्रिंग में संलग्न करने के लिए एक नई स्ट्रिंग का पुनर्गठन होता है, इसलिए यह आपके तारों की लंबाई (दोनों की योग) के लिए कुछ रैखिक खर्च करता है।

तो - आपके प्रश्न के लिए:

दूसरा दृष्टिकोण तेज होगा, लेकिन यह कम पढ़ने योग्य और बनाए रखने के लिए कठिन है। जैसा कि मैंने कहा, आपके विशिष्ट मामले में आप शायद अंतर नहीं देखेंगे।


नीचे उदाहरण देखें:

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

static void case1() {
    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() {
    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() {
    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();
        }
}

आउटपुट:

10000 concatenations के लिए औसत समय: 0.096 एमएस औसत
10000 concatenations के लिए औसत समय: 0.185 एमएस औसत
10000 concatenations के लिए औसत समय: 0.327 एमएस औसत
10000 concatenations के लिए औसत समय: 0.501 एमएस औसत
10000 concatenations के लिए औसत समय: 0.656 एमएस औसत
लंबाई की स्ट्रिंग बनाई गई: 170045 एमएस में 1 9 000000
10000 concatenations के लिए औसत समय: 0.21 एमएस औसत
10000 concatenations के लिए औसत समय: 0.652 एमएस औसत
10000 concatenations के लिए औसत समय: 1.129 एमएस औसत
10000 concatenations के लिए औसत समय: 1.727 एमएस औसत
10000 concatenations के लिए औसत समय: 2.302 एमएस औसत
लंबाई की स्ट्रिंग बनाई गई: 1950000 60279 एमएस में
10000 concatenations के लिए औसत समय: 0.002 एमएस औसत
10000 concatenations के लिए औसत समय: 0.002 एमएस औसत
10000 concatenations के लिए औसत समय: 0.002 एमएस औसत
10000 concatenations के लिए औसत समय: 0.002 एमएस औसत
10000 concatenations के लिए औसत समय: 0.002 एमएस औसत
लंबाई की स्ट्रिंग बनाई गई: 10000 में 1950000

जैसे-जैसे स्ट्रिंग की लंबाई बढ़ जाती है, वैसे ही कॉन्सटेनेशन समय भी होता है।
वह जगह है जहां StringBuilder निश्चित रूप से जरूरी है।
जैसा कि आप देखते हैं, concatenation: UUID.randomUUID()+"---" , वास्तव में समय को प्रभावित नहीं करता है।

पीएस: मुझे नहीं लगता कि जावा में स्ट्रिंगबिल्डर का उपयोग कब करना है, वास्तव में इसका एक डुप्लिकेट है।
यह प्रश्न toString() बारे में बात करता है जो ज्यादातर बार विशाल तारों के संयोजन को निष्पादित नहीं करता है।


मुझे लगता है कि हमें स्ट्रिंगबिल्डर एपेंड दृष्टिकोण के साथ जाना चाहिए। कारण है

  1. स्ट्रिंग कॉन्सटेनेट प्रत्येक बार एक नई स्ट्रिंग ऑब्जेक्ट बनाएगा (जैसे स्ट्रिंग अपरिवर्तनीय वस्तु है), इसलिए यह 3 ऑब्जेक्ट्स बनाएगा।

  2. स्ट्रिंग बिल्डर के साथ केवल एक ऑब्जेक्ट बनाया जाएगा [स्ट्रिंगबिल्डर म्यूटटेबल है] और आगे की स्ट्रिंग इसमें संलग्न हो जाती है।


मैं पसंद करता हूं:

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

... क्योंकि यह छोटा और पठनीय है।

मैं इसे गति के लिए अनुकूलित नहीं करता जबतक कि आप इसे बहुत अधिक दोहराने वाली गणना के साथ लूप के अंदर उपयोग नहीं करते हैं और प्रदर्शन अंतर को मापते हैं।

मैं मानता हूं कि अगर आपको बहुत से पैरामीटर आउटपुट करना पड़ता है, तो यह फॉर्म भ्रमित हो सकता है (जैसे टिप्पणियों में से एक कहते हैं)। इस मामले में मैं एक और अधिक पठनीय रूप में स्विच करूँगा (शायद ToStringBuilder बी के उत्तर से लिया गया अपाचे-कॉमन्स के ToStringBuilder का उपयोग करके) और फिर प्रदर्शन को अनदेखा करें।


मैंने प्रदर्शन की तुलना करने के लिए चार अलग-अलग दृष्टिकोण की तुलना की। मुझे नहीं पता कि जीसी के साथ क्या होता है, लेकिन मेरे लिए महत्वपूर्ण बात समय है। कंपाइलर यहां महत्वपूर्ण कारक है। मैंने windows8.1 प्लेटफार्म के तहत jdk1.8.0_45 का उपयोग किया।

concatWithPlusOperator = 8
concatWithBuilder = 130
concatWithConcat = 127
concatStringFormat = 3737
concatWithBuilder2 = 46


public class StringConcatenationBenchmark {

private static final int MAX_LOOP_COUNT = 1000000;

public static void main(String[] args) {

    int loopCount = 0;
    long t1 = System.currentTimeMillis();
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithPlusOperator();
        loopCount++;
    }
    long t2 = System.currentTimeMillis();
    System.out.println("concatWithPlusOperator = " + (t2 - t1));

    long t3 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder();
        loopCount++;
    }
    long t4 = System.currentTimeMillis();
    System.out.println("concatWithBuilder = " + (t4 - t3));

    long t5 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithConcat();
        loopCount++;
    }
    long t6 = System.currentTimeMillis();
    System.out.println("concatWithConcat = " + (t6 - t5));

    long t7 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatStringFormat();
        loopCount++;
    }
    long t8 = System.currentTimeMillis();
    System.out.println("concatStringFormat = " + (t8 - t7));

    long t9 = System.currentTimeMillis();
    loopCount = 0;
    while (loopCount < MAX_LOOP_COUNT) {
        concatWithBuilder2();
        loopCount++;
    }
    long t10 = System.currentTimeMillis();
    System.out.println("concatWithBuilder2 = " + (t10 - t9));
}

private static void concatStringFormat() {
    String s = String.format("%s %s %s %s ", "String", "String", "String", "String");
}

private static void concatWithConcat() {
    String s = "String".concat("String").concat("String").concat("String");
}

private static void concatWithBuilder() {
    StringBuilder builder=new StringBuilder("String");
    builder.append("String").append("String").append("String");
    String s = builder.toString();
}

private static void concatWithBuilder2() {
    String s = new StringBuilder("String").append("String").append("String").append("String").toString();
}

private static void concatWithPlusOperator() {
    String s = "String" + "String" + "String" + "String";
}
}

संस्करण 1 बेहतर है क्योंकि यह छोटा है और संकलक वास्तव में इसे संस्करण 2 में बदल देगा - कोई भी प्रदर्शन अंतर नहीं।

सबसे महत्वपूर्ण बात यह है कि हमारे पास केवल 3 गुण हैं, इससे कोई फर्क नहीं पड़ता है, लेकिन आप किस बिंदु पर कंसल्ट से बिल्डर तक स्विच करते हैं?

उस बिंदु पर जहां आप एक लूप में संयोजित हो रहे हैं - आमतौर पर जब संकलक StringBuilder को स्वयं ही प्रतिस्थापित नहीं कर सकता है।







stringbuilder