जावा 8 स्ट्रीम - कम बनाम एकत्रित करें


143

आप कब उपयोग करेंगे collect()बनाम reduce()? क्या किसी के पास अच्छा, ठोस उदाहरण है जब एक रास्ता या दूसरे पर जाना निश्चित रूप से बेहतर है?

Javadoc उल्लेख करता है कि इकट्ठा () एक पारस्परिक कमी है

यह देखते हुए कि यह एक परिवर्तनशील कमी है, मुझे लगता है कि इसे सिंक्रनाइज़ेशन (आंतरिक रूप से) की आवश्यकता होती है, जो बदले में, प्रदर्शन के लिए हानिकारक हो सकता है। वर्तमान reduce()में कम में हर कदम के बाद वापसी के लिए एक नई डेटा संरचना बनाने की लागत पर अधिक आसानी से समानांतर है।

उपरोक्त कथन हालांकि अनुमान हैं और मैं इसमें एक विशेषज्ञ से बात करना चाहूंगा।


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

1
एंजेलिका लैंगर द्वारा "स्ट्रीम इन जावा 8: रिड्यूस बनाम कलेक्ट" भी देखें - youtube.com/watch?v=oWlWEKNM5Aw
MasterJoe2

जवाबों:


115

reduce" फोल्ड " ऑपरेशन है, यह स्ट्रीम में प्रत्येक तत्व पर एक बाइनरी ऑपरेटर लागू करता है जहां ऑपरेटर के लिए पहला तर्क पिछले एप्लिकेशन का रिटर्न मान है और दूसरा तर्क वर्तमान स्ट्रीम तत्व है।

collectएक एकत्रीकरण ऑपरेशन है जहां एक "संग्रह" बनाया जाता है और प्रत्येक तत्व उस संग्रह में "जोड़ा" जाता है। स्ट्रीम के विभिन्न हिस्सों में संग्रह को एक साथ जोड़ा जाता है।

दस्तावेज़ आप लिंक किए गए दो अलग-अलग दृष्टिकोण रखने के लिए कारण देता है:

यदि हम तारों की एक धारा लेना चाहते थे और उन्हें एक ही लंबी स्ट्रिंग में समेटना चाहते थे, तो हम इसे साधारण कमी के साथ प्राप्त कर सकते थे:

 String concatenated = strings.reduce("", String::concat)  

हमें वांछित परिणाम मिलेगा, और यह समानांतर में भी काम करेगा। हालाँकि, हम प्रदर्शन के बारे में खुश नहीं हो सकते हैं! इस तरह के कार्यान्वयन से स्ट्रिंग की नकल का एक बड़ा सौदा होगा, और पात्रों की संख्या में रन टाइम O (n ^ 2) होगा। एक अधिक प्रदर्शन करने वाला दृष्टिकोण परिणाम को स्ट्रींगबर्ल में संचित करना होगा, जो तार संचय के लिए एक परस्पर कंटेनर है। हम एक ही तकनीक का उपयोग उत्परिवर्तनीय कमी को समानांतर करने के लिए कर सकते हैं जैसा कि हम साधारण कमी के साथ करते हैं।

तो मुद्दा यह है कि दोनों मामलों में समानांतरता समान है लेकिन reduceहम मामले को स्ट्रीम तत्वों पर लागू करते हैं। में collectमामले में हम एक परिवर्तनशील कंटेनर के लिए समारोह लागू होते हैं।


1
यदि संग्रह के लिए यह मामला है: "एक अधिक प्रदर्शनकारी दृष्टिकोण परिणामों को एक स्ट्रिंगबर्ल में संचित करना होगा" तो हम कभी कम क्यों करेंगे?
jimhooker2002

2
@ Jimhooker2002 ने इसे पुनः प्रसारित किया। यदि आप कहते हैं, उत्पाद की गणना करते हैं, तो कमी फ़ंक्शन को समानांतर में विभाजित धाराओं पर लागू किया जा सकता है और फिर अंत में एक साथ जोड़ा जा सकता है। कम करने की प्रक्रिया हमेशा प्रकार में धारा के रूप में परिणाम करती है। जब आप परिणाम को एक अलग कंटेनर में इकट्ठा करना चाहते हैं, तो इसका उपयोग किया जाता है, अर्थात जब परिणाम स्ट्रीम के लिए एक अलग प्रकार होता है। इसका यह फायदा है कि कंटेनर का एक भी उदाहरण प्रत्येक विभाजित स्ट्रीम के लिए उपयोग किया जा सकता है लेकिन कंटेनर को अंत में संयुक्त करने के लिए नुकसान की आवश्यकता होती है।
बोरिस स्पाइडर

1
@ उत्पाद उदाहरण में jimhooker2002, intहै अपरिवर्तनीय है ताकि आप आसानी से एक कलेक्ट ऑपरेशन का प्रयोग नहीं कर सकते हैं। आप एक गंदे हैक का उपयोग कर सकते हैं जैसे एक AtomicIntegerया कुछ कस्टम का उपयोग करें IntWrapperलेकिन आप क्यों करेंगे? एक गुना ऑपरेशन बस एक एकत्रित ऑपरेशन के लिए अलग है।
बोरिस स्पाइडर

17
एक अन्य reduceविधि भी है , जहां आप धारा के तत्वों से भिन्न प्रकार की वस्तुओं को वापस कर सकते हैं।
डमरू

1
एक और मामला जहां यू कम करने के बजाय कलेक्ट का उपयोग करेगा जब ऑपरेशन कम करने के लिए एक संग्रह में तत्वों को जोड़ना शामिल होता है, तो हर बार जब आपका संचायक फ़ंक्शन किसी तत्व को संसाधित करता है, तो यह एक नया संग्रह बनाता है जिसमें तत्व शामिल होता है, जो अक्षम है।
रघु

40

कारण बस इतना है कि:

  • collect() केवल उत्परिवर्तनीय परिणाम वस्तुओं के साथ काम कर सकते हैं।
  • reduce()को अपरिवर्तनीय परिणाम वस्तुओं के साथ काम करने के लिए डिज़ाइन किया गया है

" reduce()अपरिवर्तनीय" उदाहरण के साथ

public class Employee {
  private Integer salary;
  public Employee(String aSalary){
    this.salary = new Integer(aSalary);
  }
  public Integer getSalary(){
    return this.salary;
  }
}

@Test
public void testReduceWithImmutable(){
  List<Employee> list = new LinkedList<>();
  list.add(new Employee("1"));
  list.add(new Employee("2"));
  list.add(new Employee("3"));

  Integer sum = list
  .stream()
  .map(Employee::getSalary)
  .reduce(0, (Integer a, Integer b) -> Integer.sum(a, b));

  assertEquals(Integer.valueOf(6), sum);
}

" collect()परस्पर के साथ" उदाहरण

उदाहरण के लिए आप मैन्युअल रूप से उपयोग कर एक योग की गणना करना चाहते हैं तो collect()इसके साथ काम नहीं कर सकते हैं BigDecimal, लेकिन केवल के साथ MutableIntसे org.apache.commons.lang.mutableउदाहरण के लिए। देख:

public class Employee {
  private MutableInt salary;
  public Employee(String aSalary){
    this.salary = new MutableInt(aSalary);
  }
  public MutableInt getSalary(){
    return this.salary;
  }
}

@Test
public void testCollectWithMutable(){
  List<Employee> list = new LinkedList<>();
  list.add(new Employee("1"));
  list.add(new Employee("2"));

  MutableInt sum = list.stream().collect(
    MutableInt::new, 
    (MutableInt container, Employee employee) -> 
      container.add(employee.getSalary().intValue())
    , 
    MutableInt::add);
  assertEquals(new MutableInt(3), sum);
}

यह काम करता है क्योंकि संचायक container.add(employee.getSalary().intValue()); परिणाम के साथ एक नई वस्तु को वापस करने के लिए नहीं माना जाता है, लेकिन containerप्रकार के उत्परिवर्तनीय की स्थिति को बदलने के लिए MutableInt

यदि आप BigDecimalइसके बजाय उपयोग करना चाहते हैं तो आप विधि का containerउपयोग नहीं कर सकते क्योंकि यह अपरिवर्तनीय नहीं होगा । (इसके अलावा काम नहीं करेगा क्योंकि कोई खाली कंस्ट्रक्टर नहीं है)collect()container.add(employee.getSalary());containerBigDecimalBigDecimal::newBigDecimal


2
ध्यान दें कि आप एक Integerकंस्ट्रक्टर ( new Integer(6)) का उपयोग कर रहे हैं , जिसे बाद के जावा संस्करणों में चित्रित किया गया है।
एमसी सम्राट

1
अच्छी पकड़ @MCEmiple! मैंने इसे बदल दिया हैInteger.valueOf(6)
सैंड्रो

@ सैंड्रो - मैं भ्रमित हूं। आप यह क्यों कहते हैं कि इकट्ठा () केवल उत्परिवर्तित वस्तुओं के साथ काम करता है? मैंने इसका इस्तेमाल स्ट्रिंग्स को सुगम बनाने के लिए किया। स्ट्रिंग allNames = staff.stream () (.map (कर्मचारी :: getNameString)
मास्टरजो 2

1
@ MasterJoe2 यह सरल है। संक्षेप में - कार्यान्वयन अभी भी उपयोग करता है StringBuilderजो कि परिवर्तनशील है। देखें: hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…
सैंड्रो

30

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

समस्या को स्पष्ट करने के लिए, मान लीजिए कि आप Collectors.toList()एक साधारण कमी का उपयोग करके प्राप्त करना चाहते हैं

List<Integer> numbers = stream.reduce(
        new ArrayList<Integer>(),
        (List<Integer> l, Integer e) -> {
            l.add(e);
            return l;
        },
        (List<Integer> l1, List<Integer> l2) -> {
            l1.addAll(l2);
            return l1;
        });

इस के बराबर है Collectors.toList()। हालाँकि, इस मामले में आप म्यूट करते हैं List<Integer>। जैसा कि हम जानते हैं कि ArrayListयह थ्रेड-सुरक्षित नहीं है, और न ही इसे से मानों को जोड़ने / हटाने के लिए सुरक्षित है, ताकि आप या तो समवर्ती अपवाद या ArrayIndexOutOfBoundsExceptionया किसी भी प्रकार का अपवाद प्राप्त करेंगे (विशेषकर जब समानांतर में चलते हैं) जब आप सूची या कॉम्बिनेटर को अपडेट करते हैं। सूचियों को मर्ज करने का प्रयास करता है क्योंकि आप पूर्णांकों को इसमें जमा (जोड़) कर सूची को परिवर्तित कर रहे हैं। यदि आप इस थ्रेड-सेफ़ को सुरक्षित बनाना चाहते हैं, तो आपको हर बार एक नई सूची पास करनी होगी जो प्रदर्शन को ख़राब कर देगी।

इसके विपरीत, Collectors.toList()एक समान फैशन में काम करता है। हालाँकि, यह थ्रेड सुरक्षा की गारंटी देता है जब आप सूची में मान जमा करते हैं। विधि के लिए प्रलेखनcollect से :

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

तो आपके प्रश्न का उत्तर देने के लिए:

आप कब उपयोग करेंगे collect()बनाम reduce()?

यदि आप इस तरह के रूप में अपरिवर्तनीय मान हो ints, doubles, Stringsतो सामान्य कमी सिर्फ ठीक काम करता है। हालाँकि, यदि आपको reduceअपने मानों को एक List(म्यूटेबल डेटा स्ट्रक्चर) कहना है, तो आपको collectविधि के साथ म्यूटेबल कमी का उपयोग करना होगा ।


कोड स्निपेट में मुझे लगता है कि समस्या यह है कि यह पहचान लेगा (इस मामले में एक ArrayList का एक उदाहरण) और मान लें कि यह "अपरिवर्तनीय" है, इसलिए वे xथ्रेड शुरू कर सकते हैं , प्रत्येक "पहचान में जोड़ना" फिर एक साथ संयोजन। अच्छा उदाहरण।
रोजगारपैक

हम समवर्ती संशोधन अपवाद क्यों प्राप्त करेंगे, कॉलिंग धाराएँ केवल धारावाहिक धारावाहिक है और इसका अर्थ है कि एकल थ्रेड और कॉम्बिनर फ़ंक्शन द्वारा संसाधित किया जाना बिल्कुल भी नहीं है?
अमरनाथ

public static void main(String[] args) { List<Integer> l = new ArrayList<>(); l.add(1); l.add(10); l.add(3); l.add(-3); l.add(-4); List<Integer> numbers = l.stream().reduce( new ArrayList<Integer>(), (List<Integer> l2, Integer e) -> { l2.add(e); return l2; }, (List<Integer> l1, List<Integer> l2) -> { l1.addAll(l2); return l1; });for(Integer i:numbers)System.out.println(i); } }मैंने कोशिश की और ccm अपवाद नहीं मिला
अमरनाथ ने

@amarnathharish समस्या तब होती है जब आप इसे समानांतर में चलाने का प्रयास करते हैं और कई सूत्र एक ही सूची तक पहुंचने का प्रयास करते हैं
जॉर्ज

11

धारा को <- b <- c <- d होने दें

कमी में,

आपके पास ((a # b) # c) # d होगा

जहां # वह दिलचस्प ऑपरेशन है जो आप करना चाहते हैं।

संग्रह में,

आपके कलेक्टर के पास किसी प्रकार का संग्रह संरचना K होगा।

K उपभोग करता है a। K तब ख का उपभोग करता है। K तो सी का सेवन करता है। के तो भस्म d।

अंत में, आप K से पूछते हैं कि अंतिम परिणाम क्या है।

के तो आपको देता है।


2

वे रनटाइम के दौरान संभावित मेमोरी फ़ुटप्रिंट में बहुत भिन्न होते हैं । collect()संग्रह करते समय और सभी डेटा को संग्रह में रखता है , reduce()स्पष्ट रूप से आपको यह निर्दिष्ट करने के लिए कहता है कि स्ट्रीम के माध्यम से इसे बनाने वाले डेटा को कैसे कम किया जाए।

उदाहरण के लिए, यदि आप किसी फ़ाइल के कुछ डेटा को पढ़ना चाहते हैं, तो उसे प्रोसेस करें और इसे कुछ डेटाबेस में डालें, आप इसके समान जावा स्ट्रीम कोड के साथ समाप्त हो सकते हैं:

streamDataFromFile(file)
            .map(data -> processData(data))
            .map(result -> database.save(result))
            .collect(Collectors.toList());

इस स्थिति में, हम collect()जावा को डेटा को स्ट्रीम करने के लिए मजबूर करते हैं और इसे डेटाबेस में परिणाम को बचाने के लिए बनाते हैं। collect()डेटा के बिना कभी पढ़ा नहीं जाता है और कभी संग्रहीत नहीं किया जाता है।

यह कोड खुशी से java.lang.OutOfMemoryError: Java heap spaceरनटाइम त्रुटि उत्पन्न करता है, यदि फ़ाइल का आकार काफी बड़ा है या ढेर का आकार काफी कम है। स्पष्ट कारण यह है कि यह उन सभी डेटा को स्टैक करने की कोशिश करता है जो इसे स्ट्रीम के माध्यम से बनाते हैं (और, वास्तव में, पहले से ही डेटाबेस में संग्रहीत किया गया है) परिणामस्वरूप संग्रह में और यह ढेर को उड़ा देता है।

हालांकि, अगर आप को बदलने के collect()साथ reduce()- यह नहीं एक समस्या अब और के रूप में उत्तरार्द्ध को कम करने और सभी डेटा है कि यह माध्यम से किया छोड़ दी जाएगी किया जाएगा।

प्रस्तुत उदाहरण में, बस collect()कुछ के साथ बदलें reduce:

.reduce(0L, (aLong, result) -> aLong, (aLong1, aLong2) -> aLong1);

गणना करने के लिए आपको देखभाल करने की भी आवश्यकता नहीं है resultक्योंकि जावा एक शुद्ध FP (कार्यात्मक प्रोग्रामिंग) भाषा नहीं है और संभव साइड-इफेक्ट्स के कारण स्ट्रीम के नीचे उपयोग नहीं किए जा रहे डेटा को ऑप्टिमाइज़ नहीं कर सकता है। ।


3
यदि आपको अपने db सेव के परिणामों की परवाह नहीं है, तो आपको forEach का उपयोग करना चाहिए ... आपको कम उपयोग करने की आवश्यकता नहीं है। जब तक यह दृष्टांत उद्देश्यों के लिए नहीं था।
डेवडेलस्टीन

2

यहाँ कोड उदाहरण है

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().reduce((x,y) -> {
        System.out.println(String.format("x=%d,y=%d",x,y));
        return (x + y);
    }).get();

Println (राशि);

यहाँ निष्पादित परिणाम है:

x=1,y=2
x=3,y=3
x=6,y=4
x=10,y=5
x=15,y=6
x=21,y=7
28

फ़ंक्शन को कम करें दो मापदंडों को संभालें, पहला पैरामीटर है पिछला रिटर्न मान इंट्री स्ट्रीम, दूसरा पैरामीटर स्ट्रीम में वर्तमान गणना मूल्य है, यह पहले मूल्य और वर्तमान मूल्य को अगले कैच्यूलेशन में पहले मूल्य के रूप में योग करता है।


0

डॉक्स के अनुसार

कम करना () संग्राहक बहु-स्तरीय कमी में उपयोग किए जाने पर सबसे अधिक उपयोगी होते हैं, समूहीकरण या विभाजन के बहाव से। एक स्ट्रीम पर एक साधारण कमी करने के लिए, इसके बजाय Stream.reduce (BinaryOperator) का उपयोग करें।

इसलिए मूल रूप से आप reducing()केवल तभी उपयोग करेंगे जब किसी संग्रह के लिए मजबूर किया जाए। यहाँ एक और उदाहरण है :

 For example, given a stream of Person, to calculate the longest last name 
 of residents in each city:

    Comparator<String> byLength = Comparator.comparing(String::length);
    Map<String, String> longestLastNameByCity
        = personList.stream().collect(groupingBy(Person::getCity,
            reducing("", Person::getLastName, BinaryOperator.maxBy(byLength))));

इस ट्यूटोरियल के अनुसार कम करना कभी-कभी कम कुशल होता है

कम करने वाला ऑपरेशन हमेशा एक नया मूल्य देता है। हालाँकि, संचायक फ़ंक्शन भी एक धारा के एक तत्व को संसाधित करने के लिए हर बार एक नया मान देता है। मान लीजिए कि आप किसी स्ट्रीम के तत्वों को अधिक जटिल ऑब्जेक्ट में कम करना चाहते हैं, जैसे संग्रह। यह आपके आवेदन के प्रदर्शन में बाधा बन सकता है। यदि आपके कम किए गए ऑपरेशन में संग्रह में तत्वों को जोड़ना शामिल है, तो हर बार जब आपका संचायक फ़ंक्शन एक तत्व को संसाधित करता है, तो यह एक नया संग्रह बनाता है जिसमें तत्व शामिल होता है, जो अक्षम है। इसके बजाय आपके लिए मौजूदा संग्रह को अपडेट करना अधिक कुशल होगा। आप इसे Stream.collect पद्धति से कर सकते हैं, जिसका अगला भाग वर्णन करता है ...

तो पहचान को कम परिदृश्य में "पुन: उपयोग" किया जाता है, इसलिए .reduceयदि संभव हो तो साथ जाने के लिए थोड़ा अधिक कुशल ।

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