Chúng tôi đã có một vấn đề tương tự để giải quyết. Chúng tôi muốn lấy một luồng lớn hơn bộ nhớ hệ thống (lặp qua tất cả các đối tượng trong cơ sở dữ liệu) và ngẫu nhiên hóa thứ tự tốt nhất có thể - chúng tôi nghĩ rằng sẽ ổn khi đệm 10.000 mục và ngẫu nhiên hóa chúng.
Mục tiêu là một chức năng trong một luồng.
Trong số các giải pháp được đề xuất ở đây, dường như có một loạt các lựa chọn:
- Sử dụng các thư viện bổ sung không phải java 8 khác nhau
- Bắt đầu với thứ gì đó không phải là luồng - ví dụ: danh sách truy cập ngẫu nhiên
- Có một luồng có thể được phân chia dễ dàng trong một bộ tách sóng
Bản năng của chúng tôi ban đầu là sử dụng một bộ sưu tập tùy chỉnh, nhưng điều này đồng nghĩa với việc bỏ phát trực tuyến. Giải pháp bộ sưu tập tùy chỉnh ở trên rất tốt và chúng tôi gần như đã sử dụng nó.
Đây là một giải pháp gian lận bằng cách sử dụng thực tế là Stream
s có thể cung cấp cho bạn một giải pháp Iterator
mà bạn có thể sử dụng như một lối thoát để cho phép bạn làm điều gì đó bổ sung mà các luồng không hỗ trợ. Nó Iterator
được chuyển đổi lại thành một luồng bằng cách sử dụng một bit StreamSupport
phép thuật khác của Java 8 .
/**
* 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());
}
}
}
Một ví dụ đơn giản về cách sử dụng này sẽ như sau:
@Test
public void getsBatches() {
BatchingIterator.batchedStreamOf(Stream.of("A","B","C","D","E","F"), 3)
.forEach(System.out::println);
}
Các bản in trên
[A, B, C]
[D, E, F]
Đối với trường hợp sử dụng của chúng tôi, chúng tôi muốn xáo trộn các lô và sau đó giữ chúng dưới dạng một luồng - nó trông như thế này:
@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);
}
Điều này xuất ra một cái gì đó giống như (nó ngẫu nhiên, rất khác nhau mỗi lần)
A
C
B
E
D
F
Nước sốt bí mật ở đây là luôn có một luồng, vì vậy bạn có thể thao tác trên một loạt các lô hoặc làm một cái gì đó cho từng lô và sau đó flatMap
nó trở lại một luồng. Thậm chí tốt hơn, tất cả những điều trên chỉ chạy dưới dạng biểu thức cuối cùng forEach
hoặc collect
hoặc các biểu thức kết thúc khác KÉO dữ liệu qua luồng.
Hóa ra đó iterator
là một loại thao tác kết thúc đặc biệt trên một luồng và không khiến toàn bộ luồng chạy và đi vào bộ nhớ! Cảm ơn các chàng trai Java 8 về một thiết kế tuyệt vời!