मुझे धाराओं का उपयोग कब करना चाहिए?


99

मैं अभी एक सवाल भर आया जब एक Listऔर इसकी stream()विधि का उपयोग कर । जबकि मुझे पता है कि उनका उपयोग कैसे करना है, मुझे यकीन नहीं है कि उनका उपयोग कब करना है।

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

यह निश्चित रूप से, प्रति से एक कठिन कार्य नहीं है। लेकिन मुझे आश्चर्य है कि क्या मुझे स्ट्रीम, या फॉर-इन (लूप) का उपयोग करना चाहिए।

सूचि

private static final List<String> EXCLUDE_PATHS = Arrays.asList(new String[]{
    "my/path/one",
    "my/path/two"
});

उदाहरण - धारा

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream()
                        .map(String::toLowerCase)
                        .filter(path::contains)
                        .collect(Collectors.toList())
                        .size() > 0;
}

उदाहरण - फॉर-हर लूप

private boolean isExcluded(String path){
    for (String excludePath : EXCLUDE_PATHS) {
        if(path.contains(excludePath.toLowerCase())){
            return true;
        }
    }
    return false;
}

ध्यान दें कि pathपैरामीटर हमेशा निचला होता है

मेरा पहला अनुमान है कि फॉर-एप्रोच तेज़ है, क्योंकि अगर शर्त पूरी हो जाती है तो लूप तुरंत लौट आएगा। जबकि फ़िल्टरिंग को पूरा करने के लिए स्ट्रीम अभी भी सभी सूची प्रविष्टियों पर लूप करेगी।

क्या मेरी धारणा सही है? यदि हां, तो मैं क्यों (या बल्कि कब ) का उपयोग करूंगा stream()?


11
धाराएं पारंपरिक लूप की तुलना में अधिक अभिव्यंजक और पठनीय हैं। बाद में आपको अगर-तब और स्थितियों आदि की आंतरिकता के बारे में सावधान रहने की जरूरत है, तो धारा की अभिव्यक्ति बहुत स्पष्ट है: फ़ाइल नाम को निचले मामलों में परिवर्तित करें, फिर किसी चीज़ से फ़िल्टर करें और फिर गणना करें, इकट्ठा करें, आदि: एक बहुत ही पुनरावृत्त। संगणना के प्रवाह की अभिव्यक्ति।
जीन-बैप्टिस्ट युनुस

12
यहां कोई जरूरत नहीं new String[]{…}है। बस उपयोगArrays.asList("my/path/one", "my/path/two")
Holger

4
यदि आपका स्रोत है String[], तो कॉल करने की कोई आवश्यकता नहीं है Arrays.asList। आप केवल सरणी का उपयोग करके स्ट्रीम कर सकते हैं Arrays.stream(array)। वैसे, मुझे isExcludedपरीक्षण के उद्देश्य को पूरी तरह से समझने में कठिनाइयाँ हैं। क्या यह वास्तव में दिलचस्प है कि क्या तत्व का EXCLUDE_PATHSशाब्दिक अर्थ पथ के भीतर कहीं निहित है? यानी isExcluded("my/path/one/foo/bar/baz")वापसी होगी true, साथ ही isExcluded("foo/bar/baz/my/path/one/")
होल्गर

3
महान, मुझे इस Arrays.streamविधि की जानकारी नहीं थी, इस ओर इशारा करने के लिए धन्यवाद। दरअसल, मैंने जो उदाहरण पोस्ट किया है वह मेरे अलावा किसी और के लिए काफी बेकार लगता है। मैं isExcludedविधि के व्यवहार से अवगत हूँ , लेकिन यह वास्तव में सिर्फ कुछ है जो मुझे खुद के लिए चाहिए, इस प्रकार, आपके प्रश्न का उत्तर देने के लिए: हाँ , यह उन कारणों के लिए दिलचस्प है जिनका मैं उल्लेख नहीं करना चाहता, क्योंकि यह दायरे में फिट नहीं होंगे। मूल प्रश्न का।
mcuenez

1
toLowerCaseस्थिरांक पर आवेदन क्यों किया जाता है जो पहले से ही लो-केस है? क्या इसे pathतर्क पर लागू नहीं किया जाना चाहिए ?
सेबेस्टियन रेडल

जवाबों:


78

आपकी धारणा सही है। आपका स्ट्रीम कार्यान्वयन फ़ॉर-लूप की तुलना में धीमा है।

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

EXCLUDE_PATHS.stream()  
                               .map(String::toLowerCase)
                               .anyMatch(path::contains);

यह आइटम्स के माध्यम से पुनरावृत्ति करता है, String::toLowerCaseएक-एक करके वस्तुओं को फ़िल्टर करता है और मेल खाने वाले पहले आइटम पर समाप्त होता है

दोनों collect()और anyMatch()टर्मिनल संचालन कर रहे हैं। anyMatch()हालांकि, पहले पाए गए आइटम पर बाहर निकलता है, जबकि collect()सभी वस्तुओं को संसाधित करने की आवश्यकता होती है।


2
बहुत बढ़िया, के findFirst()साथ संयोजन के बारे में पता नहीं था filter()। जाहिरा तौर पर, मुझे नहीं पता कि धाराओं का उपयोग कैसे किया जाए, जैसा कि मैंने सोचा था।
mcuenez

4
वेब पर स्ट्रीम एपीआई प्रदर्शन के बारे में कुछ दिलचस्प ब्लॉग लेख और प्रस्तुतियां हैं, जो मुझे यह समझने में बहुत मददगार लगीं कि यह सामान हुड के नीचे कैसे काम करता है। मैं निश्चित रूप से बस थोड़ा शोध करने की सलाह दे सकता हूं, यदि आप उस में रुचि रखते हैं।
स्टीफन प्राइज

आपके संपादन के बाद, मुझे लगता है कि आपका उत्तर वही है जिसे स्वीकार किया जाना चाहिए, क्योंकि आपने दूसरे उत्तर की टिप्पणियों में भी मेरे प्रश्न का उत्तर दिया है। हालांकि, मैं कोड पोस्ट करने के लिए @ rvit34 को कुछ क्रेडिट देना चाहूंगा :-)
mcuenez

34

धाराओं का उपयोग करने या न करने का निर्णय प्रदर्शन विचार से नहीं, बल्कि पठनीयता द्वारा लिया जाना चाहिए। जब यह वास्तव में प्रदर्शन की बात आती है, तो अन्य विचार हैं।

अपने .filter(path::contains).collect(Collectors.toList()).size() > 0दृष्टिकोण के साथ , आप सभी तत्वों को संसाधित कर रहे हैं और उन्हें एक अस्थायी में एकत्रित कर रहे हैंList आकार की तुलना करने से पहले , फिर भी, यह शायद ही कभी दो तत्वों से युक्त स्ट्रीम के लिए मायने रखता है।

उपयोग करने से .map(String::toLowerCase).anyMatch(path::contains)सीपीयू चक्र और मेमोरी को बचाया जा सकता है, यदि आपके पास तत्वों की एक बड़ी संख्या है। फिर भी, यह प्रत्येक Stringको अपने लोअरकेस प्रतिनिधित्व में बदल देता है, जब तक कि एक मैच नहीं मिलता। जाहिर है, उपयोग करने का एक बिंदु है

private static final List<String> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .collect(Collectors.toList());

private boolean isExcluded(String path) {
    return EXCLUDE_PATHS.stream().anyMatch(path::contains);
}

बजाय। इसलिए आपको प्रत्येक इनवोकेशन में लोकेस में रूपांतरण को दोहराना नहीं है isExcluded। यदि तत्वों की संख्या EXCLUDE_PATHSया तार की लंबाई वास्तव में बड़ी हो जाती है, तो आप उपयोग करने पर विचार कर सकते हैं

private static final List<Predicate<String>> EXCLUDE_PATHS =
    Stream.of("my/path/one", "my/path/two").map(String::toLowerCase)
          .map(s -> Pattern.compile(s, Pattern.LITERAL).asPredicate())
          .collect(Collectors.toList());

private boolean isExcluded(String path){
    return EXCLUDE_PATHS.stream().anyMatch(p -> p.test(path));
}

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

बेशक, यह केवल तभी भुगतान करता है जब तैयारी में लगने वाले समय की भरपाई के लिए पर्याप्त बाद के परीक्षण हों। यह निर्धारित करना कि क्या यह मामला होगा, वास्तविक प्रदर्शन विचारों में से एक है, पहले सवाल के अलावा कि क्या यह ऑपरेशन कभी भी महत्वपूर्ण प्रदर्शन होगा। यह सवाल नहीं है कि क्या धाराओं का उपयोग करना है याfor छोरों ।

वैसे, ऊपर दिए गए कोड उदाहरण आपके मूल कोड का तर्क रखते हैं, जो मुझे संदेहास्पद लगता है। आपकी isExcludedविधि वापस आती है true, यदि निर्दिष्ट पथ में सूची में कोई भी तत्व शामिल है, तो यह रिटर्न के trueलिए /some/prefix/to/my/path/one, साथ ही साथ my/path/one/and/some/suffixया यहां तक ​​कि /some/prefix/to/my/path/one/and/some/suffix

यहां तक ​​कि dummy/path/onerousइसे containsस्ट्रिंग के रूप में मानदंड पूरा करना माना जाता है my/path/one...


संभव प्रदर्शन अनुकूलन पर अच्छी अंतर्दृष्टि, धन्यवाद। आपके उत्तर के अंतिम भाग के बारे में: यदि आपकी टिप्पणी का मेरा उत्तर संतोषजनक नहीं था, तो मेरे उदाहरण कोड को दूसरों के लिए एक मात्र सहायक के रूप में समझें कि मैं जो पूछ रहा हूं - वह वास्तविक कोड होने के बजाय। इसके अलावा, आप हमेशा प्रश्न को संपादित कर सकते हैं, यदि आपके पास एक बेहतर उदाहरण है।
mcuenez

3
मैं आपकी टिप्पणी लेता हूं कि यह ऑपरेशन वही है जो आप वास्तव में चाहते हैं, इसलिए इसे बदलने की कोई आवश्यकता नहीं है। मैं सिर्फ भविष्य के पाठकों के लिए अंतिम खंड रखूंगा, इसलिए वे जानते हैं कि यह एक विशिष्ट ऑपरेशन नहीं है, लेकिन यह भी, कि यह पहले से ही चर्चा की गई है और आगे की टिप्पणियों की आवश्यकता नहीं है ...
Holger

वास्तव में स्ट्रीम मेमोरी ऑप्टिमाइज़ेशन के लिए उपयोग करने के लिए एकदम सही हैं जब वर्किंग मेमोरी की मात्रा सर्वर सीमा को तोड़ रही है
ColacX

21

हाँ। तुम सही हो। आपके स्ट्रीम अप्रोच में कुछ ओवरहेड होगा। लेकिन आप इस तरह के निर्माण का उपयोग कर सकते हैं:

private boolean isExcluded(String path) {
    return  EXCLUDE_PATHS.stream().map(String::toLowerCase).anyMatch(path::contains);
}

धाराओं का उपयोग करने का मुख्य कारण यह है कि वे आपके कोड को सरल और पढ़ने में आसान बनाते हैं।


3
के लिए anyMatchएक शॉर्टकट है filter(...).findFirst().isPresent()?
mcuenez

6
हाँ यही है! यह मेरे पहले सुझाव से भी बेहतर है।
स्टीफन प्रिज़

8

जावा में धाराओं का लक्ष्य समानांतर कोड लिखने की जटिलता को सरल बनाना है। यह कार्यात्मक प्रोग्रामिंग से प्रेरित है। सीरियल स्ट्रीम सिर्फ कोड क्लीनर बनाने के लिए है।

यदि हम प्रदर्शन चाहते हैं, तो हमें समानांतर स्ट्र्रीम का उपयोग करना चाहिए, जिसे डिजाइन किया गया था। धारावाहिक, सामान्य रूप से, धीमा है।

वहाँ एक अच्छा लेख के बारे में पढ़ने के लिए है , और प्रदर्शन ForLoopStreamParallelStream

आपके कोड में हम पहले मैच पर खोज को रोकने के लिए समाप्ति विधियों का उपयोग कर सकते हैं। (AnyMatch ...)


5
ध्यान दें कि छोटी धाराओं और कुछ अन्य मामलों में, स्टार्टअप लागत के कारण एक समानांतर धारा धीमी हो सकती है। और अगर आपके पास एक आदेशित टर्मिनल ऑपरेशन है, बजाय एक अनियंत्रित समानांतर एक के रूप में, अंत में resynchronization।
19 नवंबर को CAD97

0

जैसा कि दूसरों ने कई अच्छे बिंदुओं का उल्लेख किया है, लेकिन मैं सिर्फ स्ट्रीम मूल्यांकन में आलसी मूल्यांकन का उल्लेख करना चाहता हूं । जब हम map()निचले केस पथों की एक धारा बनाने के लिए करते हैं, तो हम तुरंत पूरी स्ट्रीम नहीं बना रहे हैं, इसके बजाय स्ट्रीम को आलसी तरीके से बनाया गया है , यही कारण है कि प्रदर्शन लूप के लिए पारंपरिक के बराबर होना चाहिए। यह एक पूर्ण स्कैनिंग नहीं कर रहा है, map()और anyMatch()एक ही समय में निष्पादित किया जाता है। एक बार जब anyMatch()यह सच हो जाता है, तो इसे शॉर्ट-सर्कुलेट किया जाएगा।

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