Java 8 Stream với xử lý hàng loạt


95

Tôi có một tệp lớn chứa danh sách các mục.

Tôi muốn tạo một loạt các mục, thực hiện một yêu cầu HTTP với lô này (tất cả các mục đều cần thiết làm tham số trong yêu cầu HTTP). Tôi có thể làm điều đó rất dễ dàng với một forvòng lặp, nhưng với tư cách là người yêu thích Java 8, tôi muốn thử viết điều này bằng khung Stream của Java 8 (và gặt hái những lợi ích của việc xử lý lười biếng).

Thí dụ:

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);

Tôi muốn làm một cái gì đó từ rất lâu lazyFileStream.group(500).map(processBatch).collect(toList())

cách nào tốt nhất để làm việc này?


Tôi không thể tìm ra cách thực hiện nhóm, xin lỗi, nhưng các dòng Files # sẽ đọc nội dung của tệp một cách lười biếng.
Toby

1
vì vậy về cơ bản bạn cần một nghịch đảo của flatMap(+ một bản đồ phẳng bổ sung để thu gọn các luồng một lần nữa)? Tôi không nghĩ rằng một cái gì đó như vậy tồn tại như một phương pháp thuận tiện trong thư viện chuẩn. Hoặc là bạn sẽ phải tìm một lib bên thứ 3 hoặc viết riêng của bạn dựa trên spliterators và / hoặc một nhà sưu tập phát ra một dòng suối
the8472

3
Có thể bạn có thể kết hợp Stream.generatevới reader::readLinelimit, nhưng vấn đề là các luồng không hoạt động tốt với Ngoại lệ. Ngoài ra, điều này có lẽ không phải là song song tốt. Tôi nghĩ rằng forvòng lặp vẫn là lựa chọn tốt nhất.
tobias_k

Tôi vừa thêm một mã ví dụ. Tôi không nghĩ rằng flatMap là cách để đi. Nghi ngờ rằng tôi có thể phải viết Spliterator tùy chỉnh
Andy Dang

1
Tôi đang đặt ra thuật ngữ "Lạm dụng luồng" cho những câu hỏi như thế này.
kervin

Câu trả lời:


13

Ghi chú! Giải pháp này đọc toàn bộ tệp trước khi chạy forEach.

Bạn có thể làm điều đó với jOOλ , một thư viện mở rộng các luồng Java 8 cho các trường hợp sử dụng luồng tuần tự, đơn luồng:

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

Đằng sau hậu trường, zipWithIndex()chỉ là:

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());
}

... trong khi groupBy()API tiện lợi cho:

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

(Tuyên bố từ chối trách nhiệm: Tôi làm việc cho công ty đằng sau jOOλ)


Chà. Đây chính xác là những gì tôi đang tìm kiếm. Hệ thống của chúng tôi thường xử lý dữ liệu suối theo thứ tự vì vậy đây sẽ là một sự phù hợp tốt để chuyển sang Java 8.
Andy Đặng

16
Lưu ý rằng giải pháp này không cần thiết lưu trữ các dòng đầu vào toàn bộ đến trung gian Map(không giống như, ví dụ, giải pháp Ben Manes)
Tagir Valeev

124

Để hoàn thiện, đây là một giải pháp Ổi .

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

Trong câu hỏi, bộ sưu tập đã có sẵn vì vậy luồng không cần thiết và nó có thể được viết là,

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

11
Lists.partitionlà một biến thể khác mà tôi nên đề cập đến.
Ben Manes

2
điều này là lười biếng, phải không? nó sẽ không gọi toàn bộ Streamvào bộ nhớ trước khi xử lý lô liên quan
orirab

1
@orirab vâng. Nó lười biếng giữa các lô, vì nó sẽ tiêu thụ batchSizecác phần tử mỗi lần lặp.
Ben Manes


58

Cũng có thể triển khai Java-8 thuần túy:

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));

Lưu ý rằng không giống như JOOl, nó có thể hoạt động song song một cách độc đáo (miễn là của bạn datalà danh sách truy cập ngẫu nhiên).


1
nếu dữ liệu của bạn thực sự là một luồng thì sao? (giả sử các dòng trong một tệp, hoặc thậm chí từ mạng).
Omry Yadan

6
@OmryYadan, câu hỏi về việc có đầu vào từ List(xem data.size(), data.get()trong câu hỏi). Tôi đang trả lời câu hỏi được hỏi. Nếu bạn có câu hỏi khác, hãy hỏi nó thay thế (mặc dù tôi nghĩ rằng câu hỏi phát trực tiếp cũng đã được hỏi).
Tagir Valeev

1
Làm thế nào để xử lý các lô song song?
soup_boy 22/02/17

37

Giải pháp Java 8 thuần túy :

Chúng tôi có thể tạo một bộ sưu tập tùy chỉnh để thực hiện điều này một cách trang nhã, quá trình này cần một batch sizevà một Consumerđể xử lý từng lô:

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();
    }
}

Sau đó tùy ý tạo một lớp tiện ích trợ giúp:

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);
    }
}

Ví dụ sử dụng:

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));

Tôi cũng đã đăng mã của mình trên GitHub, nếu ai đó muốn xem:

Liên kết tới Github


1
Đây là một giải pháp tốt, trừ khi bạn không thể đưa tất cả các phần tử từ luồng của mình vào bộ nhớ. Ngoài ra, nó sẽ không hoạt động trên các luồng vô tận - phương thức thu thập là thiết bị đầu cuối, có nghĩa là thay vì tạo luồng theo lô, nó sẽ đợi cho đến khi luồng hoàn thành và sau đó xử lý kết quả theo lô.
Alex Ackerman

2
@AlexAckerman một luồng vô hạn sẽ có nghĩa là bộ kết thúc không bao giờ được gọi, nhưng bộ tích lũy sẽ vẫn được gọi vì vậy các mục vẫn sẽ được xử lý. Ngoài ra, nó chỉ yêu cầu kích thước lô của các mục trong bộ nhớ cùng một lúc.
Solubris

@Solubris, bạn nói đúng! Thật tệ, cảm ơn vì đã chỉ ra điều này - tôi sẽ không xóa nhận xét để tham khảo, nếu ai đó có cùng ý tưởng về cách thức hoạt động của phương pháp thu thập.
Alex Ackerman

Danh sách được gửi đến người tiêu dùng nên được sao chép để đảm bảo việc sửa đổi được an toàn, ví dụ: batchProcessor.accept (copyOf (ts))
Solubris 10/09/19

19

Tôi đã viết một Spliterator tùy chỉnh cho các tình huống như thế này. Nó sẽ điền vào các danh sách có kích thước nhất định từ Luồng đầu vào. Ưu điểm của cách tiếp cận này là nó sẽ thực hiện xử lý lười biếng và nó sẽ hoạt động với các chức năng luồng khác.

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();
    }

}

Thực sự hữu ích. Nếu ai đó muốn thực thi trên một số tiêu chí tùy chỉnh (ví dụ kích thước của bộ sưu tập theo byte), sau đó bạn có thể ủy ngữ tùy chỉnh của bạn và sử dụng nó trong cho vòng lặp như một điều kiện (IMHO vòng lặp while sẽ dễ đọc hơn rồi)
pls

Tôi không chắc cách triển khai đó là chính xác. Ví dụ: nếu luồng cơ sở là SUBSIZEDphần tách được trả về trySplitcó thể có nhiều mục hơn so với trước khi phân tách (nếu quá trình tách xảy ra ở giữa lô).
Malt

@Malt nếu hiểu biết của tôi Spliteratorslà đúng, thì trySplitphải luôn phân vùng dữ liệu thành hai phần gần bằng nhau để kết quả không bao giờ lớn hơn ban đầu?
Bruce Hamilton

@BruceHamilton Thật không may, theo tài liệu, các phần không thể gần như bằng nhau. Chúng phải bằng nhau: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.
Malt

Vâng, điều đó phù hợp với hiểu biết của tôi về tách Spliterator. Tuy nhiên, tôi đang gặp khó khăn trong việc hiểu "các phần được trả về từ trySplit có thể có nhiều mục hơn so với trước khi chia", bạn có thể giải thích thêm về ý của bạn ở đó không?
Bruce Hamilton

13

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à Streams có thể cung cấp cho bạn một giải pháp Iteratormà 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 StreamSupportphé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 đó flatMapnó 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 forEachhoặc collecthoặc các biểu thức kết thúc khác KÉO dữ liệu qua luồng.

Hóa ra đó iteratorlà 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!


Và rất tốt khi bạn lặp lại đầy đủ từng lô khi nó được thu thập và duy trì đến một List—bạn không thể trì hoãn việc lặp lại các phần tử trong lô bởi vì người tiêu dùng có thể muốn bỏ qua toàn bộ lô và nếu bạn không sử dụng thì chúng sẽ không bị bỏ qua rất xa. (Tôi đã thực hiện một trong những trong C #, mặc dù nó là dễ dàng hơn đáng kể.)
ErikE

9

Bạn cũng có thể sử dụng RxJava :

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

hoặc là

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

hoặc là

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

8

Bạn cũng có thể xem qua cyclops-react , tôi là tác giả của thư viện này. Nó triển khai giao diện jOOλ (và bằng phần mở rộng JDK 8 Streams), nhưng không giống như JDK 8 Parallel Streams, nó tập trung vào các hoạt động Không đồng bộ (chẳng hạn như có khả năng chặn các cuộc gọi Async I / O). Ngược lại, JDK Parallel Streams tập trung vào tính song song dữ liệu cho các hoạt động ràng buộc CPU. Nó hoạt động bằng cách quản lý tổng hợp các nhiệm vụ dựa trên Tương lai, nhưng cung cấp API Luồng mở rộng tiêu chuẩn cho người dùng cuối.

Mã mẫu này có thể giúp bạn bắt đầu

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

Có bài hướng dẫn đánh lô tại đây

Và một Hướng dẫn tổng quát hơn ở đây

Để sử dụng Nhóm chủ đề của riêng bạn (có lẽ thích hợp hơn để chặn I / O), bạn có thể bắt đầu xử lý với

     LazyReact reactor = new LazyReact(40);

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

3

Ví dụ thuần túy Java 8 cũng hoạt động với các luồng song song.

Cách sử dụng:

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

Khai báo và thực hiện phương pháp:

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

Công bằng mà nói, hãy xem giải pháp Vavr thanh lịch :

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

1

Ví dụ đơn giản sử dụng 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=====================");               
        }           
    }       
}

Câu trả lời của Bruce thì toàn diện hơn, nhưng tôi đang tìm kiếm thứ gì đó nhanh chóng và tiện lợi để xử lý một loạt các tệp.


1

đây là một giải pháp java thuần túy được đánh giá một cách lười biếng.

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

Bạn có thể sử dụng apache.commons:

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

Phần phân vùng được thực hiện một cách dễ dàng nhưng sau khi danh sách được phân vùng, bạn sẽ có được những lợi ích khi làm việc với các luồng (ví dụ: sử dụng các luồng song song, thêm bộ lọc, v.v.). Các câu trả lời khác đề xuất các giải pháp phức tạp hơn nhưng đôi khi khả năng đọc và khả năng bảo trì quan trọng hơn (và đôi khi chúng không :-))


Không chắc người downvoted nhưng sẽ được tốt đẹp để hiểu tại sao .. Tôi đưa ra một câu trả lời rằng, bổ sung các câu trả lời khác cho những người không có khả năng sử dụng ổi
Tal Joffe

Bạn đang xử lý danh sách ở đây, không phải luồng.
Drakemor

@Drakemor Tôi đang xử lý một luồng danh sách phụ. chú ý đến chức năng stream () gọi
Tal Joffe

Nhưng trước tiên, bạn biến nó thành một danh sách các danh sách phụ, danh sách này sẽ không hoạt động chính xác đối với dữ liệu được phát trực tuyến thực sự . Đây là tham chiếu đến phân vùng: commons.apache.org/proper/commons-collections/apidocs/org/…
Drakemor

1
TBH Tôi không hoàn toàn hiểu được lập luận của bạn nhưng tôi đoán chúng ta có thể đồng ý và không đồng ý. Tôi đã chỉnh sửa câu trả lời của mình để phản ánh cuộc trò chuyện của chúng ta ở đây. Cảm ơn vì cuộc thảo luận
Tal Joffe

1

Nó có thể dễ dàng thực hiện bằng Reactor :

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

0

Với Java 8com.google.common.collect.Lists, bạn có thể làm điều gì đó như:

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());
    }
}

Trong đây Tlà loại mục trong danh sách đầu vào và Uloại mục trong danh sách đầu ra

Và bạn có thể sử dụng nó như thế này:

List<String> userKeys = [... list of user keys]
List<Users> users = BatchProcessingUtil.process(
    userKeys,
    10, // Batch Size
    partialKeys -> service.getUsers(partialKeys)
);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.