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 Stream
s dapat memberi Anda Iterator
yang dapat Anda gunakan sebagai jalan keluar untuk membiarkan Anda melakukan sesuatu yang ekstra yang tidak didukung oleh aliran. Itu Iterator
diubah kembali ke aliran menggunakan sedikit StreamSupport
sihir 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 flatMap
kembali ke aliran. Lebih baik lagi, semua hal di atas hanya berjalan sebagai ekspresi akhir forEach
atau collect
atau ekspresi penghentian lainnya PULL data melalui aliran.
Ternyata itu iterator
adalah 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!