Java 8 बैच प्रोसेसिंग के साथ स्ट्रीम


94

मेरे पास एक बड़ी फाइल है जिसमें वस्तुओं की एक सूची है।

मैं वस्तुओं का एक बैच बनाना चाहूंगा, इस बैच के साथ एक HTTP अनुरोध करें (सभी आइटम HTTP अनुरोध में पैरामीटर के रूप में आवश्यक हैं)। मैं इसे बहुत आसानी से एक forलूप के साथ कर सकता हूं , लेकिन जावा 8 प्रेमी के रूप में, मैं इसे जावा 8 की स्ट्रीम फ्रेमवर्क के साथ लिखने की कोशिश करना चाहता हूं (और आलसी प्रसंस्करण के लाभों को पुनः प्राप्त करना)।

उदाहरण:

List<String> batch = new ArrayList<>(BATCH_SIZE);
for (int i = 0; i < data.size(); i++) {
  batch.add(data.get(i));
  if (batch.size() == BATCH_SIZE) process(batch);
}

if (batch.size() > 0) process(batch);

मैं कुछ लंबी लाइन करना चाहता हूं lazyFileStream.group(500).map(processBatch).collect(toList())

क्या सबसे अच्छा तरीका होगा यह करने का?


मुझे पता नहीं है कि ग्रुपिंग, सॉरी कैसे करें, लेकिन फाइल # लाइन्स फाइल की सामग्री को आसानी से पढ़ सकते हैं।
टॉबी

1
तो आपको मूल रूप से flatMap(+ फिर से स्ट्रीम को खत्म करने के लिए एक अतिरिक्त फ्लैटपाइप) का उलटा चाहिए ? मुझे नहीं लगता कि मानक पुस्तकालय में एक सुविधाजनक विधि के रूप में ऐसा कुछ मौजूद है। या तो आपको एक 3 पार्टी का परिवाद ढूंढना होगा या अपने स्वयं के विभाजन के आधार पर लिखना होगा और / या धाराओं की धारा का उत्सर्जन करने वाला एक कलेक्टर
the8472

3
हो सकता है आप को जोड़ सकते हैं Stream.generateके साथ reader::readLineऔर limit, लेकिन समस्या यह धाराओं अपवाद के साथ अच्छी तरह से जाना नहीं है। इसके अलावा, यह संभवतः समानांतर नहीं है। मुझे लगता है कि forलूप अभी भी सबसे अच्छा विकल्प है।
तोबिअस_क

मैंने अभी एक उदाहरण कोड जोड़ा है। मुझे नहीं लगता कि फ्लैटपाइप जाने का रास्ता है। यह संदेह करते हुए कि मुझे एक कस्टम स्प्लिटेटर
एंडी डांग

1
मैं इस तरह के सवालों के लिए "स्ट्रीम दुरुपयोग" शब्द गढ़ रहा हूं।
केरिन

जवाबों:


13

ध्यान दें! यह समाधान forEach चलाने से पहले पूरी फ़ाइल पढ़ता है।

आप इसे jOOλ के साथ कर सकते हैं , एक लाइब्रेरी जो जावा 8 स्ट्रीम को सिंगल-थ्रेडेड, अनुक्रमिक स्ट्रीम उपयोग-मामलों के लिए विस्तारित करती है:

Seq.seq(lazyFileStream)              // Seq<String>
   .zipWithIndex()                   // Seq<Tuple2<String, Long>>
   .groupBy(tuple -> tuple.v2 / 500) // Map<Long, List<String>>
   .forEach((index, batch) -> {
       process(batch);
   });

पर्दे के पीछे, zipWithIndex()बस है:

static <T> Seq<Tuple2<T, Long>> zipWithIndex(Stream<T> stream) {
    final Iterator<T> it = stream.iterator();

    class ZipWithIndex implements Iterator<Tuple2<T, Long>> {
        long index;

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        @Override
        public Tuple2<T, Long> next() {
            return tuple(it.next(), index++);
        }
    }

    return seq(new ZipWithIndex());
}

... जबकि groupBy()API सुविधा इसके लिए है:

default <K> Map<K, List<T>> groupBy(Function<? super T, ? extends K> classifier) {
    return collect(Collectors.groupingBy(classifier));
}

(डिस्क्लेमर: मैं कंपनी के पीछे काम करता हूं)


वाह। यह वास्तव में मैं क्या देख रहा हूँ। हमारी प्रणाली आम तौर पर अनुक्रम में डेटा धाराओं को संसाधित करती है, इसलिए यह जावा 8 में जाने के लिए एक अच्छा फिट होगा।
एंडी डांग

16
ध्यान दें कि यह समाधान अनावश्यक रूप से पूरे इनपुट स्ट्रीम को इंटरमीडिएट में संग्रहीत करता है Map(उदाहरण के लिए, बेन मेन्स समाधान)
टैगिर वलेव

123

पूर्णता के लिए, यहां एक अमरूद समाधान है।

Iterators.partition(stream.iterator(), batchSize).forEachRemaining(this::process);

प्रश्न में संग्रह उपलब्ध है इसलिए किसी स्ट्रीम की आवश्यकता नहीं है और इसे इस प्रकार लिखा जा सकता है,

Iterables.partition(data, batchSize).forEach(this::process);

11
Lists.partitionएक और भिन्नता है जिसका मुझे उल्लेख करना चाहिए था।
बेन मैनेस

2
यह आलसी है, है ना? यह Streamसंबंधित बैच
orirab

1
@ सौरभ हां। यह बैचों के बीच आलसी है, क्योंकि इसमें batchSizeप्रति पुनरावृत्ति तत्वों की खपत होगी ।
बेन मैनेस 19

क्या आप कृपया एक नज़र stackoverflow.com/questions/58666190/…
gstackoverflow

58

शुद्ध जावा -8 कार्यान्वयन भी संभव है:

int BATCH = 500;
IntStream.range(0, (data.size()+BATCH-1)/BATCH)
         .mapToObj(i -> data.subList(i*BATCH, Math.min(data.size(), (i+1)*BATCH)))
         .forEach(batch -> process(batch));

ध्यान दें कि JOOl के विपरीत यह समानांतर रूप से अच्छी तरह से काम कर सकता है (बशर्ते कि आपकी dataयादृच्छिक पहुंच सूची हो)।


1
क्या होगा यदि आपका डेटा वास्तव में एक धारा है? (एक फ़ाइल में या नेटवर्क से भी लाइनें कह सकते हैं)।
ओमी यदन

6
@OmryYadan, सवाल से इनपुट होने के बारे में था List(देखें data.size(), data.get()प्रश्न में)। मैं पूछे गए सवाल का जवाब दे रहा हूं। यदि आपके पास एक और प्रश्न है, तो इसके बजाय पूछें (हालांकि मुझे लगता है कि स्ट्रीम प्रश्न भी पहले से ही पूछा गया था)।
टैगिर वलेव

1
समानांतर में बैचों को कैसे संसाधित किया जाए?
सूप_बॉय

36

शुद्ध जावा 8 समाधान :

हम जो एक में लेता है यह सुंदर ढंग से करने के लिए एक कस्टम कलेक्टर, बना सकते हैं batch sizeऔर एक Consumerप्रत्येक बैच प्रक्रिया करने के लिए:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.*;
import java.util.stream.Collector;

import static java.util.Objects.requireNonNull;


/**
 * Collects elements in the stream and calls the supplied batch processor
 * after the configured batch size is reached.
 *
 * In case of a parallel stream, the batch processor may be called with
 * elements less than the batch size.
 *
 * The elements are not kept in memory, and the final result will be an
 * empty list.
 *
 * @param <T> Type of the elements being collected
 */
class BatchCollector<T> implements Collector<T, List<T>, List<T>> {

    private final int batchSize;
    private final Consumer<List<T>> batchProcessor;


    /**
     * Constructs the batch collector
     *
     * @param batchSize the batch size after which the batchProcessor should be called
     * @param batchProcessor the batch processor which accepts batches of records to process
     */
    BatchCollector(int batchSize, Consumer<List<T>> batchProcessor) {
        batchProcessor = requireNonNull(batchProcessor);

        this.batchSize = batchSize;
        this.batchProcessor = batchProcessor;
    }

    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    public BiConsumer<List<T>, T> accumulator() {
        return (ts, t) -> {
            ts.add(t);
            if (ts.size() >= batchSize) {
                batchProcessor.accept(ts);
                ts.clear();
            }
        };
    }

    public BinaryOperator<List<T>> combiner() {
        return (ts, ots) -> {
            // process each parallel list without checking for batch size
            // avoids adding all elements of one to another
            // can be modified if a strict batching mode is required
            batchProcessor.accept(ts);
            batchProcessor.accept(ots);
            return Collections.emptyList();
        };
    }

    public Function<List<T>, List<T>> finisher() {
        return ts -> {
            batchProcessor.accept(ts);
            return Collections.emptyList();
        };
    }

    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
}

वैकल्पिक रूप से तब एक सहायक उपयोगिता वर्ग बनाएँ:

import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collector;

public class StreamUtils {

    /**
     * Creates a new batch collector
     * @param batchSize the batch size after which the batchProcessor should be called
     * @param batchProcessor the batch processor which accepts batches of records to process
     * @param <T> the type of elements being processed
     * @return a batch collector instance
     */
    public static <T> Collector<T, List<T>, List<T>> batchCollector(int batchSize, Consumer<List<T>> batchProcessor) {
        return new BatchCollector<T>(batchSize, batchProcessor);
    }
}

उदाहरण का उपयोग:

List<Integer> input = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> output = new ArrayList<>();

int batchSize = 3;
Consumer<List<Integer>> batchProcessor = xs -> output.addAll(xs);

input.stream()
     .collect(StreamUtils.batchCollector(batchSize, batchProcessor));

मैंने अपना कोड GitHub पर पोस्ट किया है, अगर कोई देखना चाहता है:

जीथुब से लिंक करें


1
यह एक अच्छा समाधान है, जब तक कि आप अपनी स्ट्रीम से सभी तत्वों को मेमोरी में फिट नहीं कर सकते। इसके अलावा यह अंतहीन धाराओं पर काम नहीं करेगा - कलेक्ट मेथड टर्मिनल है, जिसका अर्थ है कि बैचों की धारा बनाने के बजाय यह तब तक इंतजार करेगा जब तक कि धारा पूरी नहीं हो जाती है, और फिर बैचों में परिणाम की प्रक्रिया करें।
एलेक्स एकरमैन

2
@ एलेक्समैन एक अनंत धारा का अर्थ होगा कि फिनिशर को कभी भी कॉल नहीं किया जाता है, लेकिन संचायक को अभी भी बुलाया जाएगा इसलिए आइटम अभी भी संसाधित होंगे। इसके अलावा, यह केवल किसी एक समय में मेमोरी में होने के लिए आइटम के बैच आकार की आवश्यकता होती है।
सोलुब्रीस

@Solubris, आप सही हैं! मेरा बुरा, इसे इंगित करने के लिए धन्यवाद - मैं संदर्भ के लिए टिप्पणी को नहीं हटाऊंगा, अगर किसी को यह विचार है कि संग्रह विधि कैसे काम करती है।
एलेक्स एकरमैन

उपभोक्ता को भेजी गई सूची को संशोधन के लिए सुरक्षित किया जाना चाहिए, जैसे: batchProcessor.accept (copyOf (ts))
Solubris

19

मैंने इस तरह के परिदृश्यों के लिए एक कस्टम स्प्लिटरेटर लिखा। यह इनपुट स्ट्रीम से दिए गए आकार की सूचियों को भरेगा। इस दृष्टिकोण का लाभ यह है कि यह आलसी प्रसंस्करण करेगा, और यह अन्य स्ट्रीम फ़ंक्शन के साथ काम करेगा।

public static <T> Stream<List<T>> batches(Stream<T> stream, int batchSize) {
    return batchSize <= 0
        ? Stream.of(stream.collect(Collectors.toList()))
        : StreamSupport.stream(new BatchSpliterator<>(stream.spliterator(), batchSize), stream.isParallel());
}

private static class BatchSpliterator<E> implements Spliterator<List<E>> {

    private final Spliterator<E> base;
    private final int batchSize;

    public BatchSpliterator(Spliterator<E> base, int batchSize) {
        this.base = base;
        this.batchSize = batchSize;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<E>> action) {
        final List<E> batch = new ArrayList<>(batchSize);
        for (int i=0; i < batchSize && base.tryAdvance(batch::add); i++)
            ;
        if (batch.isEmpty())
            return false;
        action.accept(batch);
        return true;
    }

    @Override
    public Spliterator<List<E>> trySplit() {
        if (base.estimateSize() <= batchSize)
            return null;
        final Spliterator<E> splitBase = this.base.trySplit();
        return splitBase == null ? null
                : new BatchSpliterator<>(splitBase, batchSize);
    }

    @Override
    public long estimateSize() {
        final double baseSize = base.estimateSize();
        return baseSize == 0 ? 0
                : (long) Math.ceil(baseSize / (double) batchSize);
    }

    @Override
    public int characteristics() {
        return base.characteristics();
    }

}

वास्तव में उपयोगी है। यदि कोई व्यक्ति कुछ कस्टम मानदंडों (उदाहरण के लिए बाइट्स में संग्रह के आकार) पर बैच करना चाहता है, तो आप अपने कस्टम को डेडिकेट कर सकते हैं और इसे एक शर्त के रूप में उपयोग कर सकते हैं (imho जबकि लूप अधिक पठनीय होगा)
pls

मुझे यकीन नहीं है कि कार्यान्वयन सही है। उदाहरण के लिए, यदि बेस स्ट्रीम SUBSIZEDविभाजन trySplitसे पहले की तुलना में अधिक आइटम हो सकते हैं (यदि विभाजन बैच के बीच में होता है)।
माल्ट

@ मेट्ट अगर मेरी समझ Spliteratorsसही है, तो trySplitहमेशा डेटा को दो बराबर भागों में विभाजित करना चाहिए ताकि परिणाम मूल से बड़ा न हो?
ब्रूस हैमिल्टन

@Bruce Hamilton दुर्भाग्य से, डॉक्स के अनुसार भागों को लगभग बराबर नहीं किया जा सकता है । उन्हें बराबर होना चाहिए:if this Spliterator is SUBSIZED, then estimateSize() for this spliterator before splitting must be equal to the sum of estimateSize() for this and the returned Spliterator after splitting.
माल्ट

हां, स्प्लिटेटर विभाजन की मेरी समझ के अनुरूप है। हालाँकि, मुझे यह समझने में मुश्किल समय आ रहा है कि "स्प्लिट्स से पहले आए स्प्लिट्स में विभाजन से पहले की तुलना में अधिक आइटम कैसे हो सकते हैं", क्या आप इसका मतलब बता सकते हैं कि आपका वहां क्या मतलब है?
ब्रूस हैमिल्टन

13

हमें हल करने के लिए इसी तरह की समस्या थी। हम एक ऐसी स्ट्रीम लेना चाहते थे जो सिस्टम मेमोरी (डेटाबेस में सभी ऑब्जेक्ट्स के माध्यम से पुनरावृत्ति) से बड़ी हो और ऑर्डर को यथासंभव सर्वोत्तम रूप से रैंडम करे - हमने सोचा कि 10,000 आइटम को बफर करना और उन्हें रैंडमाइज करना ठीक रहेगा।

लक्ष्य एक फ़ंक्शन था जो एक स्ट्रीम में लिया गया था।

यहां प्रस्तावित समाधानों में, विकल्पों की एक श्रृंखला प्रतीत होती है:

  • विभिन्न गैर-जावा 8 अतिरिक्त पुस्तकालयों का उपयोग करें
  • कुछ के साथ शुरू करें जो एक धारा नहीं है - उदाहरण के लिए एक यादृच्छिक पहुँच सूची
  • एक धारा है जो एक विभाजक में आसानी से विभाजित हो सकती है

हमारी वृत्ति मूल रूप से एक कस्टम कलेक्टर का उपयोग करने के लिए थी, लेकिन इसका मतलब स्ट्रीमिंग से बाहर गिरना था। ऊपर कस्टम कलेक्टर समाधान बहुत अच्छा है और हमने लगभग इसका उपयोग किया है।

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

/**
 * An iterator which returns batches of items taken from another iterator
 */
public class BatchingIterator<T> implements Iterator<List<T>> {
    /**
     * Given a stream, convert it to a stream of batches no greater than the
     * batchSize.
     * @param originalStream to convert
     * @param batchSize maximum size of a batch
     * @param <T> type of items in the stream
     * @return a stream of batches taken sequentially from the original stream
     */
    public static <T> Stream<List<T>> batchedStreamOf(Stream<T> originalStream, int batchSize) {
        return asStream(new BatchingIterator<>(originalStream.iterator(), batchSize));
    }

    private static <T> Stream<T> asStream(Iterator<T> iterator) {
        return StreamSupport.stream(
            Spliterators.spliteratorUnknownSize(iterator,ORDERED),
            false);
    }

    private int batchSize;
    private List<T> currentBatch;
    private Iterator<T> sourceIterator;

    public BatchingIterator(Iterator<T> sourceIterator, int batchSize) {
        this.batchSize = batchSize;
        this.sourceIterator = sourceIterator;
    }

    @Override
    public boolean hasNext() {
        prepareNextBatch();
        return currentBatch!=null && !currentBatch.isEmpty();
    }

    @Override
    public List<T> next() {
        return currentBatch;
    }

    private void prepareNextBatch() {
        currentBatch = new ArrayList<>(batchSize);
        while (sourceIterator.hasNext() && currentBatch.size() < batchSize) {
            currentBatch.add(sourceIterator.next());
        }
    }
}

इसका उपयोग करने का एक सरल उदाहरण इस तरह दिखेगा:

@Test
public void getsBatches() {
    BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
        .forEach(System.out::println);
}

उपरोक्त प्रिंट

[A, B, C]
[D, E, F]

हमारे उपयोग के मामले के लिए, हम बैचों को फेरबदल करना चाहते थे और फिर उन्हें एक धारा के रूप में रखते थे - यह इस तरह दिखता था:

@Test
public void howScramblingCouldBeDone() {
    BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
        // the lambda in the map expression sucks a bit because Collections.shuffle acts on the list, rather than returning a shuffled one
        .map(list -> {
            Collections.shuffle(list); return list; })
        .flatMap(List::stream)
        .forEach(System.out::println);
}

यह कुछ इस तरह का उत्पादन करता है (यह हर बार यादृच्छिक होता है)

A
C
B
E
D
F

यहां गुप्त चटनी यह है कि हमेशा एक धारा होती है, इसलिए आप या तो बैचों की एक धारा पर काम कर सकते हैं, या प्रत्येक बैच के लिए कुछ कर सकते हैं और फिर flatMapइसे एक धारा में वापस कर सकते हैं। और भी बेहतर, ऊपर केवल के सभी चलाता है के रूप में अंतिम forEachया collectया अन्य समाप्त भाव खींचने धारा के माध्यम से डेटा।

यह पता चला है कि एक धारा पर iteratorएक विशेष प्रकार का समाप्ति ऑपरेशन है और इससे पूरी धारा नहीं चलती है और स्मृति में आ जाती है! एक शानदार डिजाइन के लिए जावा 8 लोगों का धन्यवाद!


और यह बहुत अच्छा है कि आप प्रत्येक बैच पर पूरी तरह से पुनरावृति करते हैं जब यह एकत्र होता है और जब तक आप बना रहता है तब तक Listआप बैच के तत्वों के पुनरावृत्ति को रोक नहीं सकते क्योंकि उपभोक्ता पूरे बैच को छोड़ना चाहता है, और यदि आप उपभोग नहीं करते हैं तत्वों तो वे बहुत दूर लंघन नहीं होगा। (मैंने इनमें से एक को C # में लागू किया है, हालाँकि यह काफी आसान था।)
ErikE

9

आप RxJava का भी उपयोग कर सकते हैं :

Observable.from(data).buffer(BATCH_SIZE).forEach((batch) -> process(batch));

या

Observable.from(lazyFileStream).buffer(500).map((batch) -> process(batch)).toList();

या

Observable.from(lazyFileStream).buffer(500).map(MyClass::process).toList();

8

आप साइक्लॉप्स-रिएक्शन पर भी नज़र डाल सकते हैं , मैं इस लाइब्रेरी का लेखक हूँ। यह jOOλ इंटरफ़ेस (और एक्सटेंशन JDK 8 स्ट्रीम) को लागू करता है, लेकिन JDK 8 समानांतर धाराओं के विपरीत इसमें एसिंक्रोनस संचालन (जैसे संभावित रूप से अवरुद्ध Async I / O कॉल) पर ध्यान केंद्रित किया गया है। JDK समानांतर धाराएँ, सीपीयू बाउंड संचालन के लिए डेटा समानता पर विपरीत ध्यान केंद्रित करके। यह हुड के तहत भविष्य आधारित कार्यों के समुच्चय का प्रबंधन करके काम करता है, लेकिन उपयोगकर्ताओं को समाप्त करने के लिए एक मानक विस्तारित स्ट्रीम एपीआई प्रस्तुत करता है।

यह नमूना कोड आपको आरंभ करने में मदद कर सकता है

LazyFutureStream.parallelCommonBuilder()
                .react(data)
                .grouped(BATCH_SIZE)                  
                .map(this::process)
                .run();

यहां बैचिंग पर एक ट्यूटोरियल है

और यहाँ एक अधिक सामान्य ट्यूटोरियल

अपने स्वयं के थ्रेड पूल (जो संभवतः I / O को अवरुद्ध करने के लिए अधिक उपयुक्त है) का उपयोग करने के लिए, आप के साथ प्रसंस्करण शुरू कर सकते हैं

     LazyReact reactor = new LazyReact(40);

     reactor.react(data)
            .grouped(BATCH_SIZE)                  
            .map(this::process)
            .run();

3

शुद्ध जावा 8 उदाहरण जो समानांतर धाराओं के साथ भी काम करता है।

कैसे इस्तेमाल करे:

Stream<Integer> integerStream = IntStream.range(0, 45).parallel().boxed();
CsStreamUtil.processInBatch(integerStream, 10, batch -> System.out.println("Batch: " + batch));

विधि घोषणा और कार्यान्वयन:

public static <ElementType> void processInBatch(Stream<ElementType> stream, int batchSize, Consumer<Collection<ElementType>> batchProcessor)
{
    List<ElementType> newBatch = new ArrayList<>(batchSize);

    stream.forEach(element -> {
        List<ElementType> fullBatch;

        synchronized (newBatch)
        {
            if (newBatch.size() < batchSize)
            {
                newBatch.add(element);
                return;
            }
            else
            {
                fullBatch = new ArrayList<>(newBatch);
                newBatch.clear();
                newBatch.add(element);
            }
        }

        batchProcessor.accept(fullBatch);
    });

    if (newBatch.size() > 0)
        batchProcessor.accept(new ArrayList<>(newBatch));
}

2

सभी निष्पक्षता में, सुरुचिपूर्ण Vavr समाधान पर एक नज़र डालें :

Stream.ofAll(data).grouped(BATCH_SIZE).forEach(this::process);

1

Spliterator का उपयोग करके सरल उदाहरण

    // read file into stream, try-with-resources
    try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
        //skip header
        Spliterator<String> split = stream.skip(1).spliterator();
        Chunker<String> chunker = new Chunker<String>();
        while(true) {              
            boolean more = split.tryAdvance(chunker::doSomething);
            if (!more) {
                break;
            }
        }           
    } catch (IOException e) {
        e.printStackTrace();
    }
}

static class Chunker<T> {
    int ct = 0;
    public void doSomething(T line) {
        System.out.println(ct++ + " " + line.toString());
        if (ct % 100 == 0) {
            System.out.println("====================chunk=====================");               
        }           
    }       
}

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


1

यह एक शुद्ध जावा घोल है जिसका आलस्यपूर्वक मूल्यांकन किया जाता है।

public static <T> Stream<List<T>> partition(Stream<T> stream, int batchSize){
    List<List<T>> currentBatch = new ArrayList<List<T>>(); //just to make it mutable 
    currentBatch.add(new ArrayList<T>(batchSize));
    return Stream.concat(stream
      .sequential()                   
      .map(new Function<T, List<T>>(){
          public List<T> apply(T t){
              currentBatch.get(0).add(t);
              return currentBatch.get(0).size() == batchSize ? currentBatch.set(0,new ArrayList<>(batchSize)): null;
            }
      }), Stream.generate(()->currentBatch.get(0).isEmpty()?null:currentBatch.get(0))
                .limit(1)
    ).filter(Objects::nonNull);
}

1

आप apache.commons का उपयोग कर सकते हैं:

ListUtils.partition(ListOfLines, 500).stream()
                .map(partition -> processBatch(partition)
                .collect(Collectors.toList());

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


निश्चित नहीं है कि किसने नीचा दिखाया, लेकिन यह समझने में अच्छा होगा कि क्यों .. मैंने एक जवाब दिया जो लोगों के लिए अन्य उत्तरों को पूरक था जो अमरूद का उपयोग करने में सक्षम नहीं हैं
ताल जॉफ

आप यहां एक सूची संसाधित कर रहे हैं, एक धारा नहीं।
धर्मकेर

@Drakemor मैं उप-सूचियों की एक धारा का प्रसंस्करण कर रहा हूँ। स्ट्रीम को नोटिस करें () फ़ंक्शन कॉल
ताल जॉफ़

लेकिन पहले आप इसे उप-सूचियों की सूची में बदल देते हैं, जो सही स्ट्रीम किए गए डेटा के लिए सही ढंग से काम नहीं करेगा । यहाँ विभाजन का संदर्भ दिया गया है: commons.apache.org/proper/commons-collections/apidocs/org/…
Drakemor

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

1

यह रिएक्टर का उपयोग करके आसानी से किया जा सकता है :

Flux.fromStream(fileReader.lines().onClose(() -> safeClose(fileReader)))
            .map(line -> someProcessingOfSingleLine(line))
            .buffer(BUFFER_SIZE)
            .subscribe(apiService::makeHttpRequest);

0

साथ Java 8और com.google.common.collect.Lists, आप की तरह कुछ कर सकते हैं:

public class BatchProcessingUtil {
    public static <T,U> List<U> process(List<T> data, int batchSize, Function<List<T>, List<U>> processFunction) {
        List<List<T>> batches = Lists.partition(data, batchSize);
        return batches.stream()
                .map(processFunction) // Send each batch to the process function
                .flatMap(Collection::stream) // flat results to gather them in 1 stream
                .collect(Collectors.toList());
    }
}

यहाँ Tइनपुट सूची Uमें आइटम का प्रकार और आउटपुट सूची में आइटम का प्रकार है

और आप इसे इस तरह से उपयोग कर सकते हैं:

List<String> userKeys = [... list of user keys]
List<Users> users = BatchProcessingUtil.process(
    userKeys,
    10, // Batch Size
    partialKeys -> service.getUsers(partialKeys)
);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.