SQL में StringBuilder का उपयोग करने का सही तरीका


88

मैंने अपनी परियोजना में इस तरह से कुछ sql क्वेरी का निर्माण किया है:

return (new StringBuilder("select id1, " + " id2 " + " from " + " table")).toString();

क्या यह StringBuilderअपने उद्देश्य को प्राप्त करता है , अर्थात मेमोरी का उपयोग कम करता है?

मुझे संदेह है कि, क्योंकि कंस्ट्रक्टर में '+' (स्ट्रिंग कॉन्कैट ऑपरेटर) का उपयोग किया जाता है। नीचे दिए गए कोड की तरह स्ट्रिंग का उपयोग करते हुए क्या वह समान मात्रा में मेमोरी लेगा? मुझे समझ में आया, यह प्रयोग करते समय भिन्न होता है StringBuilder.append()

return "select id1, " + " id2 " + " from " + " table";

स्मृति उपयोग में दोनों कथन समान हैं या नहीं? कृपया स्पष्ट करें।

अग्रिम में धन्यवाद!

संपादित करें:

BTW, यह मेरा कोड नहीं है । यह एक पुराने प्रोजेक्ट में मिला। इसके अलावा, क्वेरी इतनी छोटी नहीं है जितनी कि मेरे उदाहरण में दी गई है। :)


1
SQL सुरक्षा: हमेशा उपयोग PreparedStatementया कुछ इसी तरह: docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
Christophe Roussy

मेमोरी उपयोग की चीज़ के अलावा, SQL बिल्डर लाइब्रेरी का उपयोग क्यों न करें: इसके अलावा stackoverflow.com/q/370818/521799
Lukas Eder

जवाबों:


182

StringBuilder का उपयोग करने का उद्देश्य, यानी स्मृति को कम करना। क्या इसे हासिल किया गया है?

नहीं, बिलकुल नहीं। वह कोड StringBuilderसही उपयोग नहीं कर रहा है। (मुझे लगता है कि आपने इसे गलत बताया है, हालांकि; निश्चित रूप से आस id2- पास के उद्धरण नहीं हैं और table?)

ध्यान दें कि उद्देश्य (आमतौर पर) का उपयोग कुल मेमोरी के बजाय मेमोरी मंथन को कम करने के लिए किया जाता है, ताकि कचरा कलेक्टर पर जीवन को थोड़ा आसान बनाया जा सके।

क्या नीचे की तरह स्ट्रिंग का उपयोग करने के लिए मेमोरी समान होगी?

नहीं, यह आपके द्वारा उद्धृत सीधे कॉनकट की तुलना में अधिक मेमोरी मंथन का कारण होगा । (जब तक / जब तक जेवीएम ऑप्टिमाइज़र यह नहीं देखता कि StringBuilderकोड में स्पष्ट अनावश्यक है और इसे बाहर कर देता है, अगर यह हो सकता है।)

यदि उस कोड का लेखक उपयोग करना चाहता है StringBuilder (इसके लिए तर्क हैं, लेकिन इसके खिलाफ भी; इस उत्तर के अंत में ध्यान दें देखें), इसे ठीक से करने के लिए बेहतर है (यहां मैं मान रहा हूं कि वास्तव में उद्धरण नहीं हैं ) id2और table:

StringBuilder sb = new StringBuilder(some_appropriate_size);
sb.append("select id1, ");
sb.append(id2);
sb.append(" from ");
sb.append(table);
return sb.toString();

ध्यान दें कि मैंने कंस्ट्रक्टर some_appropriate_sizeमें सूचीबद्ध किया है StringBuilder, ताकि यह पूरी सामग्री के लिए पर्याप्त क्षमता के साथ शुरू हो। यदि आप निर्दिष्ट नहीं करते हैं तो डिफ़ॉल्ट आकार का उपयोग किया जाता है 16 वर्णों का होता है , जो आमतौर पर बहुत छोटा होता है और StringBuilderसूर्य / ओरेकल जेडडीके में खुद को बड़ा (IIRC) बनाने के लिए वास्तविक परिणाम प्राप्त करने में परिणाम होता है , यह अपने आप दोगुना हो जाता है [या अधिक, यदि यह जानता है कि एक विशिष्ट को संतुष्ट करने के लिए इसे और अधिक की आवश्यकता है append] हर बार जब यह कमरे से बाहर निकलता है)।

आपने सुना होगा कि अगर सूर्य / ओरेकल संकलक के साथ संकलित किया जाता है, तो स्ट्रिंग संघनन कवर के नीचे का उपयोग करेगाStringBuilder । यह सच है, यह StringBuilderसमग्र अभिव्यक्ति के लिए एक का उपयोग करेगा । लेकिन यह डिफॉल्ट कंस्ट्रक्टर का उपयोग करेगा, जिसका अर्थ अधिकांश मामलों में है, इसके लिए एक रियलाइजेशन करना होगा। हालांकि यह पढ़ना आसान है। ध्यान दें कि यह निष्कर्षों की एक श्रृंखला का सच नहीं है । उदाहरण के लिए, यह एक का उपयोग करता है :StringBuilder

return "prefix " + variable1 + " middle " + variable2 + " end";

इसका मोटे तौर पर अनुवाद होता है:

StringBuilder tmp = new StringBuilder(); // Using default 16 character size
tmp.append("prefix ");
tmp.append(variable1);
tmp.append(" middle ");
tmp.append(variable2);
tmp.append(" end");
return tmp.toString();

तो यह ठीक है, हालांकि डिफ़ॉल्ट कंस्ट्रक्टर और उसके बाद के रियलाइजेशन (ओं) को आदर्श नहीं है, ऑड्स यह काफी अच्छा है - और कंक्रीटिंग बहुत अधिक पठनीय है।

लेकिन यह केवल एक ही अभिव्यक्ति के लिए है। इसके लिए कई StringBuilderएस का उपयोग किया जाता है:

String s;
s = "prefix ";
s += variable1;
s += " middle ";
s += variable2;
s += " end";
return s;

यह कुछ इस तरह से समाप्त होता है:

String s;
StringBuilder tmp;
s = "prefix ";
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable1);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" middle ");
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(variable2);
s = tmp.toString();
tmp = new StringBuilder();
tmp.append(s);
tmp.append(" end");
s = tmp.toString();
return s;

... जो बहुत बदसूरत है।

हालांकि, यह याद रखना महत्वपूर्ण है कि सभी में, लेकिन बहुत कम मामलों में यह कोई फर्क नहीं पड़ता है और पठनीयता के साथ जा रहा है (जो स्थिरता को बढ़ाता है) एक विशिष्ट प्रदर्शन मुद्दे को छोड़कर पसंद किया जाता है।


सही है, यह बेहतर है। पैरामीटर रहित कंस्ट्रक्टर का उपयोग थोड़ा दुर्भाग्यपूर्ण है, लेकिन महत्वपूर्ण होने की संभावना नहीं है। मैं तब भी एक ही x + y + zअभिव्यक्ति का उपयोग करता StringBuilderथा जब तक कि मेरे पास यह संदेह करने का अच्छा कारण नहीं था कि यह एक महत्वपूर्ण मुद्दा होगा।
जॉन स्कीट

@Crowder को एक और संदेह है। StringBuilder sql = new StringBuilder(" XXX); sql.append("nndmn");...। इसी तरह की sql.appendलाइनें लगभग 60 लाइनें हैं। क्या यह ठीक है?
वंदू

1
@ वनाथी: ("प्रश्न", "संदेह" नहीं - यह एक सामान्य गलतफहमी है।) यह ठीक है, लेकिन शायद कई वास्तविकताओं का परिणाम होगा, क्योंकि StringBuilderशुरुआत में आपको कंस्ट्रक्टर प्लस 16 वर्णों को पारित करने वाले स्ट्रिंग के लिए पर्याप्त जगह आवंटित की जाएगी। इसलिए यदि आप 16 से अधिक वर्णों को जोड़ते हैं (यदि आप 60 वर्ष की आयु के हैं, तो आप हैं!), StringBuilderआपको कम से कम एक बार और संभवतः कई बार पुन: आवेदन करना होगा। यदि आपके पास एक उचित विचार है कि अंतिम परिणाम कितना बड़ा होगा (कहते हैं, 400 वर्ण), तो यह करना सबसे अच्छा है sql = new StringBuilder(400);(या जो भी हो) फिर appendएस करें।
टीजे क्राउडर

@ वनाथी: खुशी है कि मदद की। हाँ, अगर यह 6,000 वर्णों वाला हो, तो यह बताना StringBuilderकि पहले से ही आठ मेमोरी रियलोकेशनों के बारे में बचत होगी (प्रारंभिक स्ट्रिंग लगभग 10 वर्णों की थी, एसबी को 26 के साथ शुरू करना होगा, फिर दोगुना होकर 52 हो जाएगा, फिर 104, 208, 416, 832, 1664, 3328, और अंत में 6656)। केवल महत्वपूर्ण है अगर यह एक हॉटस्पॉट है, लेकिन फिर भी, यदि आप पहले से जानते हैं ... :-)
टीजे क्राउडर

@ टीजे क्राउडर आपके कहने का मतलब है कि मुझे बेहतर प्रदर्शन के लिए "+" ऑपरेटर का उपयोग नहीं करना चाहिए। सही? तब ओरेकल ने अपनी भाषा में "+" ऑपरेटर को क्यों जोड़ा है क्या आप विस्तृत कर सकते हैं? किसी भी तरह से आपके उत्तर के लिए मेरा उत्थान।
पटेल पटेल

38

जब आपके पास पहले से ही सभी "टुकड़े" होते हैं, तो आप जोड़ना चाहते हैं, तो उपयोग करने का कोई मतलब नहीं है StringBuilder। अपने नमूना कोड के अनुसार एक ही कॉल में उपयोग करना StringBuilder और स्ट्रिंग समवर्ती करना और भी बदतर है।

यह बेहतर होगा:

return "select id1, " + " id2 " + " from " + " table";

इस स्थिति में, स्ट्रिंग का संघनन वास्तव में संकलन-समय पर हो रहा है, इसलिए यह सम-सरल के बराबर है:

return "select id1, id2 from table";

उपयोग new StringBuilder().append("select id1, ").append(" id2 ")....toString()करना वास्तव में इस मामले में प्रदर्शन को बाधित करेगा , क्योंकि यह संकलन समय के बजाय निष्पादन समय पर प्रदर्शन करने के लिए मजबूर करता है। उफ़।

यदि वास्तविक कोड क्वेरी में मानों को शामिल करके SQL क्वेरी का निर्माण कर रहा है , तो यह एक और अलग मुद्दा है, जो कि आपको पैरामीटर के प्रश्नों का उपयोग करना चाहिए, मानों को SQL के बजाय मापदंडों में निर्दिष्ट करना चाहिए।

मेरे पास एक लेख है String/StringBuffer जो मैंने कुछ समय पहले लिखा था - StringBuilderसाथ आने से पहले । सिद्धांत StringBuilderहालांकि उसी तरह से लागू होते हैं ।


10

[[यहां कुछ अच्छे जवाब हैं लेकिन मुझे लगता है कि उनमें अभी भी थोड़ी जानकारी की कमी है। ]]

return (new StringBuilder("select id1, " + " id2 " + " from " + " table"))
     .toString();

तो जैसा कि आप बताते हैं, जो उदाहरण आप देते हैं वह एक सरलीकृत है, लेकिन चलो वैसे भी इसका विश्लेषण करते हैं। यहाँ क्या होता है संकलक वास्तव में +यहाँ काम करता है क्योंकि "select id1, " + " id2 " + " from " + " table"सभी स्थिरांक हैं। तो यह में बदल जाता है:

return new StringBuilder("select id1,  id2  from  table").toString();

इस मामले में, जाहिर है, उपयोग करने का कोई मतलब नहीं है StringBuilder। आप भी ऐसा कर सकते हैं:

// the compiler combines these constant strings
return "select id1, " + " id2 " + " from " + " table";

हालांकि, यहां तक ​​कि अगर आप किसी भी क्षेत्र या अन्य गैर-स्थिरांक को जोड़ रहे थे, तो संकलक एक आंतरिक का उपयोग करेगा StringBuilder- आपको परिभाषित करने के लिए कोई आवश्यकता नहीं है:

// an internal StringBuilder is used here
return "select id1, " + fieldName + " from " + tableName;

कवर के तहत, यह कोड में बदल जाता है जो लगभग इसके बराबर है:

StringBuilder sb = new StringBuilder("select id1, ");
sb.append(fieldName).append(" from ").append(tableName);
return sb.toString();

वास्तव में एकमात्र समय जिसका आपको StringBuilder सीधे उपयोग करने की आवश्यकता होती है, जब आपके पास सशर्त कोड होता है। उदाहरण के लिए, कोड जो निम्नलिखित की तरह दिखता है StringBuilder:

// 1 StringBuilder used in this line
String query = "select id1, " + fieldName + " from " + tableName;
if (where != null) {
   // another StringBuilder used here
   query += ' ' + where;
}

+पहली पंक्ति में एक का उपयोग करता StringBuilderउदाहरण। फिर एक +=और StringBuilderउदाहरण का उपयोग करता है । यह करना अधिक कुशल है:

// choose a good starting size to lower chances of reallocation
StringBuilder sb = new StringBuilder(64);
sb.append("select id1, ").append(fieldName).append(" from ").append(tableName);
// conditional code
if (where != null) {
   sb.append(' ').append(where);
}
return sb.toString();

दूसरी बार StringBuilderजब मैं एक का उपयोग करता हूं , जब मैं कई विधि कॉलों से एक स्ट्रिंग का निर्माण कर रहा हूं। तब मैं ऐसी विधियाँ बना सकता हूँ जो StringBuilderतर्क लेती हैं:

private void addWhere(StringBuilder sb) {
   if (where != null) {
      sb.append(' ').append(where);
   }
}

जब आप एक का उपयोग कर रहे हैं StringBuilder, तो आपको +उसी समय के किसी भी उपयोग के लिए देखना चाहिए :

sb.append("select " + fieldName);

इससे +एक और आंतरिक पैदा होगा StringBuilder। यह अवश्य होना चाहिए:

sb.append("select ").append(fieldName);

अंत में, जैसा कि @TJrowder बताते हैं, आपको हमेशा के आकार पर एक अनुमान लगाना चाहिए StringBuilder। यह char[]आंतरिक बफर के आकार को बढ़ाते समय बनाई गई वस्तुओं की संख्या पर बचाएगा ।


4

आप यह अनुमान लगाने में सही हैं कि स्ट्रिंग बिल्डर का उपयोग करने का उद्देश्य हासिल नहीं किया गया है, कम से कम इसकी पूरी सीमा तक नहीं।

हालांकि, जब संकलक अभिव्यक्ति को देखता है तो "select id1, " + " id2 " + " from " + " table"यह कोड का उत्सर्जन करता है जो वास्तव StringBuilderमें दृश्यों के पीछे बनाता है और इसे जोड़ता है, इसलिए अंतिम परिणाम उतना बुरा नहीं है।

लेकिन निश्चित रूप से उस कोड को देखने वाला कोई भी व्यक्ति यह सोचने के लिए बाध्य है कि यह एक तरह से मंदबुद्धि है।


2

आपके द्वारा पोस्ट किए गए कोड में कोई लाभ नहीं होगा, क्योंकि आप StringBuilder का दुरुपयोग कर रहे हैं। आप दोनों मामलों में एक ही स्ट्रिंग का निर्माण करते हैं। StringBuilder का उपयोग करके आप विधि +का उपयोग करके स्ट्रिंग्स पर ऑपरेशन से बच सकते हैं append। आपको इसे इस तरह से उपयोग करना चाहिए:

return new StringBuilder("select id1, ").append(" id2 ").append(" from ").append(" table").toString();

जावा में, स्ट्रिंग प्रकार वर्णों का एक अयोग्य अनुक्रम है, इसलिए जब आप दो स्ट्रिंग्स जोड़ते हैं तो वीएम एक नया स्ट्रिंग मूल्य बनाता है जिसमें दोनों ऑपरेंड अवेटेड होते हैं।

StringBuilder वर्णों का एक परस्पर अनुक्रम प्रदान करता है, जिसका उपयोग आप नई स्ट्रिंग ऑब्जेक्ट्स बनाने के बिना विभिन्न मानों या चर का उपयोग करने के लिए कर सकते हैं, और इसलिए यह कभी-कभी स्ट्रिंग्स के साथ काम करने से अधिक कुशल हो सकता है

यह कुछ उपयोगी सुविधाएँ प्रदान करता है, क्योंकि एक अन्य विधि के अंदर पैरामीटर के रूप में पारित एक चार अनुक्रम की सामग्री को बदलना, जिसे आप स्ट्रिंग्स के साथ नहीं कर सकते।

private void addWhereClause(StringBuilder sql, String column, String value) {
   //WARNING: only as an example, never append directly a value to a SQL String, or you'll be exposed to SQL Injection
   sql.append(" where ").append(column).append(" = ").append(value);
}

Http://docs.oracle.com/javase/tutorial/java/data/buffers.html पर अधिक जानकारी


1
नहीं, आपको नहीं करना चाहिए। यह उपयोग करने की तुलना में कम पठनीय है +, जिसे वैसे भी एक ही कोड में परिवर्तित किया जाएगा। StringBuilderतब उपयोगी होता है जब आप एक ही अभिव्यक्ति में सभी संगति नहीं कर सकते, लेकिन इस मामले में नहीं।
जॉन स्कीट

1
मैं समझता हूं कि प्रश्न में स्ट्रिंग एक उदाहरण के रूप में पोस्ट की गई है। इसका कोई अर्थ नहीं होगा कि "फिक्स्ड" स्ट्रिंग का निर्माण न तो स्ट्रिंगबर्ल के साथ हो और न ही अलग-अलग अंशों को जोड़ने के रूप में, जैसा कि आप इसे एक ही निरंतर "सिलेक्ट आईडी 1, टेबल से आईडी 2" में परिभाषित कर सकते हैं
टॉमस

लेकिन यहां तक ​​कि अगर चर से गैर-निरंतर मान थे, तो यह अभी भी एक का उपयोग करेगा StringBuilderयदि आप उपयोग करना चाहते थे return "select id1, " + foo + "something else" + bar;- तो ऐसा क्यों नहीं करते? सवाल यह संकेत नहीं देता है कि किसी भी चीज को पास करने की जरूरत है StringBuilder
जॉन स्कीट

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.