हमें हल करने के लिए इसी तरह की समस्या थी। हम एक ऐसी स्ट्रीम लेना चाहते थे जो सिस्टम मेमोरी (डेटाबेस में सभी ऑब्जेक्ट्स के माध्यम से पुनरावृत्ति) से बड़ी हो और ऑर्डर को यथासंभव सर्वोत्तम रूप से रैंडम करे - हमने सोचा कि 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 लोगों का धन्यवाद!