Kami memiliki masalah serupa untuk dipecahkan. Kami ingin mengambil aliran yang lebih besar dari memori sistem (melakukan iterasi melalui semua objek dalam database) dan mengacak urutannya sebaik mungkin - kami pikir tidak masalah untuk menyangga 10.000 item dan mengacaknya.
Targetnya adalah fungsi yang mengalir.
Dari solusi yang diusulkan di sini, tampaknya ada serangkaian opsi:
- Gunakan berbagai pustaka tambahan non-java 8
- Mulailah dengan sesuatu yang bukan aliran - misalnya daftar akses acak
- Memiliki aliran yang dapat dipisahkan dengan mudah dalam spliterator
Naluri kami awalnya menggunakan kolektor khusus, tetapi ini berarti berhenti streaming. Solusi kolektor khusus di atas sangat bagus dan kami hampir menggunakannya.
Berikut adalah solusi yang menipu dengan menggunakan fakta bahwa Streams dapat memberi Anda Iteratoryang dapat Anda gunakan sebagai jalan keluar untuk membiarkan Anda melakukan sesuatu yang ekstra yang tidak didukung oleh aliran. Itu Iteratordiubah kembali ke aliran menggunakan sedikit StreamSupportsihir Java 8 lainnya .
public class BatchingIterator<T> implements Iterator<List<T>> {
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());
}
}
}
Contoh sederhana penggunaan ini akan terlihat seperti ini:
@Test
public void getsBatches() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.forEach(System.out::println);
}
Cetakan di atas
[A, B, C]
[D, E, F]
Untuk kasus penggunaan kami, kami ingin mengocok kumpulan dan kemudian menyimpannya sebagai aliran - terlihat seperti ini:
@Test
public void howScramblingCouldBeDone() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.map(list -> {
Collections.shuffle(list); return list; })
.flatMap(List::stream)
.forEach(System.out::println);
}
Ini menghasilkan sesuatu seperti (ini diacak, sangat berbeda setiap saat)
A
C
B
E
D
F
Saus rahasianya di sini adalah selalu ada aliran, sehingga Anda dapat mengoperasikan aliran kumpulan, atau melakukan sesuatu untuk setiap kumpulan dan kemudian flatMapkembali ke aliran. Lebih baik lagi, semua hal di atas hanya berjalan sebagai ekspresi akhir forEachatau collectatau ekspresi penghentian lainnya PULL data melalui aliran.
Ternyata itu iteratoradalah jenis operasi pengakhiran khusus pada aliran dan tidak menyebabkan seluruh aliran berjalan dan masuk ke memori! Terima kasih kepada orang-orang Java 8 untuk desain yang brilian!