สตรีม Java 8 พร้อมการประมวลผลแบทช์


101

ฉันมีไฟล์ขนาดใหญ่ที่มีรายการ

ฉันต้องการสร้างชุดรายการสร้างคำขอ HTTP ด้วยชุดนี้ (รายการทั้งหมดจำเป็นต้องใช้เป็นพารามิเตอร์ในคำขอ HTTP) ฉันสามารถทำได้อย่างง่ายดายด้วยการforวนซ้ำ แต่ในฐานะคนรัก Java 8 ฉันต้องการลองเขียนสิ่งนี้ด้วย Stream framework ของ Java 8 (และเก็บเกี่ยวผลประโยชน์จากการประมวลผลแบบขี้เกียจ)

ตัวอย่าง:

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

ฉันต้องการทำบางสิ่งบางอย่างที่เป็นแนวยาว lazyFileStream.group(500).map(processBatch).collect(toList())

วิธีที่ดีที่สุดในการทำเช่นนี้คืออะไร?


ฉันคิดไม่ออกว่าจะทำการจัดกลุ่มอย่างไรขออภัย แต่ไฟล์ # บรรทัดจะอ่านเนื้อหาของไฟล์อย่างเกียจคร้าน
— Toby

1
ดังนั้นโดยทั่วไปคุณต้องมีการผกผันของflatMap(+ flatMap เพิ่มเติมเพื่อยุบสตรีมอีกครั้ง)? ฉันไม่คิดว่าจะเป็นวิธีที่สะดวกสบายในไลบรารีมาตรฐาน ไม่ว่าคุณจะต้องหา lib ของบุคคลที่สามหรือเขียนของคุณเองโดยอิงจากตัวแยกและ / หรือนักสะสมที่ปล่อยกระแสสตรีม
— the8472

3
บางทีคุณอาจรวมStream.generateกับreader::readLineและlimitแต่ปัญหาคือสตรีมไม่เข้ากันได้ดีกับข้อยกเว้น นอกจากนี้อาจไม่สามารถขนานกันได้ดี ฉันคิดว่าforห่วงยังคงเป็นตัวเลือกที่ดีที่สุด
— tobias_k

ฉันเพิ่งเพิ่มโค้ดตัวอย่าง ฉันไม่คิดว่า flatMap เป็นวิธีที่จะไป สงสัยว่าฉันอาจต้องเขียน Spliterator แบบกำหนดเอง
— Andy Dang

1
ฉันกำลังตั้งคำว่า "การละเมิดสตรีม" สำหรับคำถามเช่นนี้
— kervin

คำตอบ:


13

บันทึก! โซลูชันนี้อ่านไฟล์ทั้งหมดก่อนที่จะรัน forEach

คุณสามารถทำได้ด้วยjOOλไลบรารีที่ขยายสตรีม Java 8 สำหรับกรณีการใช้สตรีมแบบเธรดเดียว:

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

เบื้องหลังzipWithIndex()เป็นเพียง:

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

... ในขณะที่groupBy()API สะดวกสำหรับ:

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

(ข้อจำกัดความรับผิดชอบ: ฉันทำงานให้กับ บริษัท ที่อยู่เบื้องหลังjOOλ)


ว้าว. นี่คือสิ่งที่ฉันกำลังมองหาอย่างแน่นอน โดยปกติระบบของเราจะประมวลผลสตรีมข้อมูลตามลำดับดังนั้นจึงเป็นการดีที่จะย้ายไปที่ Java 8
— Andy Dang

16
โปรดทราบว่าโซลูชันนี้จัดเก็บสตรีมอินพุตทั้งหมดไว้ที่ตัวกลางโดยไม่จำเป็นMap(ไม่เหมือนเช่นโซลูชัน Ben Manes)
— Tagir Valeev

อันที่จริงการกำหนดจุดสิ้นสุดของชุดแรกเริ่มต้นสตรีมทั้งหมดและบัฟเฟอร์ภายใน
— Robin479

130

เพื่อความสมบูรณ์นี่คือวิธีแก้ปัญหาของฝรั่ง

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

ในคำถามคอลเลกชันนี้พร้อมใช้งานดังนั้นจึงไม่จำเป็นต้องใช้สตรีมและสามารถเขียนเป็น

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

11
Lists.partitionเป็นอีกรูปแบบหนึ่งที่ฉันควรกล่าวถึง
— Ben Manes

2
นี่มันขี้เกียจใช่มั้ย? จะไม่เรียกStreamหน่วยความจำทั้งหมดก่อนประมวลผลชุดที่เกี่ยวข้อง
— orirab

1
@orirab ใช่. มันขี้เกียจระหว่างแบทช์เนื่องจากจะใช้batchSizeองค์ประกอบต่อการวนซ้ำ
— Ben Manes


62

สามารถใช้งาน Pure Java-8 ได้:

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

โปรดทราบว่าแตกต่างจาก JOOl ตรงที่สามารถทำงานควบคู่กันได้อย่างดี (โดยที่คุณdataเป็นรายการเข้าถึงโดยสุ่ม)


1
จะเกิดอะไรขึ้นถ้าข้อมูลของคุณเป็นสตรีมจริง ๆ ? (สมมติว่าบรรทัดในไฟล์หรือแม้กระทั่งจากเครือข่าย)
— Omry Yadan

7
@OmryYadan คำถามที่เป็นเกี่ยวกับการมีข้อมูลจากList(ดูdata.size(), data.get()ในคำถาม) ฉันกำลังตอบคำถามที่ถาม หากคุณมีคำถามอื่นให้ถามแทน (แม้ว่าฉันคิดว่าคำถามสตรีมก็ถูกถามแล้วเช่นกัน)
— Tagir Valeev

1
วิธีการประมวลผลแบทช์แบบขนาน?
— soup_boy

38

โซลูชัน Pure Java 8 :

เราสามารถสร้างตัวสะสมแบบกำหนดเองเพื่อทำสิ่งนี้ได้อย่างสวยงามซึ่งใช้เวลา a batch sizeและ a Consumerในการประมวลผลแต่ละชุด:

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

เลือกที่จะสร้างคลาสยูทิลิตี้ตัวช่วย:

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

ตัวอย่างการใช้งาน:

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

ฉันได้โพสต์รหัสของฉันบน GitHub แล้วเช่นกันหากใครต้องการดู:

ลิงก์ไปยัง Github


1
นี่เป็นทางออกที่ดีเว้นแต่คุณจะไม่สามารถใส่องค์ประกอบทั้งหมดจากสตรีมลงในหน่วยความจำได้ นอกจากนี้มันจะไม่ทำงานบนสตรีมที่ไม่มีที่สิ้นสุด - วิธีการรวบรวมคือเทอร์มินัลซึ่งหมายความว่าแทนที่จะสร้างสตรีมของแบทช์จะรอจนกว่าสตรีมจะเสร็จสมบูรณ์จากนั้นประมวลผลผลลัพธ์เป็นแบทช์
— Alex Ackerman

2
@AlexAckerman สตรีมที่ไม่มีที่สิ้นสุดจะหมายถึงหมัดเด็ดไม่เคยถูกเรียก แต่ตัวสะสมจะยังคงถูกเรียกดังนั้นรายการจะยังคงถูกประมวลผล นอกจากนี้ยังต้องใช้ขนาดแบทช์ของรายการในหน่วยความจำในครั้งเดียวเท่านั้น
— Solubris

@ คุณ Solubris คุณพูดถูก! ฉันไม่ดีขอบคุณที่ชี้ให้เห็น - ฉันจะไม่ลบความคิดเห็นสำหรับการอ้างอิงหากมีคนคิดเหมือนกันว่าวิธีการรวบรวมทำงานอย่างไร
— Alex Ackerman

รายการที่ส่งไปยังผู้บริโภคควรถูกคัดลอกเพื่อให้การดัดแปลงนั้นปลอดภัยเช่น batchProcessor.accept (copyOf (ts))
— Solubris

19

ฉันเขียน Spliterator แบบกำหนดเองสำหรับสถานการณ์เช่นนี้ จะเติมรายการขนาดที่กำหนดจากสตรีมอินพุต ข้อดีของวิธีนี้คือการประมวลผลแบบขี้เกียจและจะทำงานร่วมกับฟังก์ชันสตรีมอื่น ๆ

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

}

เป็นประโยชน์จริงๆ หากมีคนต้องการแบทช์กับเกณฑ์ที่กำหนดเองบางอย่าง (เช่นขนาดของคอลเลกชันเป็นไบต์) คุณสามารถมอบหมายเพรดิเคตที่กำหนดเองของคุณและใช้ใน for-loop เป็นเงื่อนไขได้ (imho ในขณะที่ลูปจะอ่านได้มากขึ้น)
— pls

ฉันไม่แน่ใจว่าการนำไปใช้นั้นถูกต้อง ตัวอย่างเช่นหากสตรีมพื้นฐานคือSUBSIZEDการแยกที่ส่งคืนtrySplitอาจมีรายการมากกว่าก่อนการแยก (หากการแยกเกิดขึ้นตรงกลางชุดงาน)
— Malt

@ มอลต์ถ้าความเข้าใจของฉันSpliteratorsถูกต้องtrySplitควรแบ่งข้อมูลออกเป็นสองส่วนเท่า ๆ กันเสมอดังนั้นผลลัพธ์จึงไม่ควรใหญ่กว่าต้นฉบับ?
— Bruce Hamilton

แต่น่าเสียดายที่ @BruceHamilton ตามเอกสารในส่วนที่ไม่สามารถจะประมาณเท่ากับ พวกเขาจะต้องเท่ากับ: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.
— มอลต์

ใช่นั่นสอดคล้องกับความเข้าใจของฉันเกี่ยวกับการแยก Spliterator อย่างไรก็ตามฉันมีปัญหาในการทำความเข้าใจว่า "การแยกที่กลับมาจาก trySplit สามารถมีรายการมากกว่าก่อนการแยก" ได้อย่างไรคุณช่วยอธิบายให้ละเอียดว่าคุณหมายถึงอะไรที่นั่นได้อย่างไร
— Bruce Hamilton

14

เรามีปัญหาที่คล้ายกันในการแก้ไข เราต้องการใช้สตรีมที่ใหญ่กว่าหน่วยความจำระบบ (วนซ้ำผ่านวัตถุทั้งหมดในฐานข้อมูล) และสุ่มลำดับที่ดีที่สุดเท่าที่จะเป็นไปได้ - เราคิดว่าจะสามารถบัฟเฟอร์ 10,000 รายการและสุ่มได้

เป้าหมายเป็นฟังก์ชันที่รับกระแส

จากโซลูชันที่เสนอที่นี่ดูเหมือนจะมีตัวเลือกมากมาย:

  • ใช้ไลบรารีเพิ่มเติมที่ไม่ใช่ java 8 ต่างๆ
  • เริ่มต้นด้วยสิ่งที่ไม่ใช่สตรีมเช่นรายการเข้าถึงโดยสุ่ม
  • มีสตรีมที่สามารถแยกได้อย่างง่ายดายใน Spliterator

สัญชาตญาณของเราเดิมทีจะใช้นักสะสมที่กำหนดเอง แต่นั่นหมายถึงการเลิกสตรีม โซลูชันตัวสะสมแบบกำหนดเองด้านบนนั้นดีมากและเราเกือบจะใช้แล้ว

นี่คือวิธีแก้ปัญหาที่กลโกงโดยใช้ความจริงที่ว่าStreams สามารถให้คุณIteratorใช้เป็นช่องทางหลบหนีเพื่อให้คุณทำอะไรพิเศษที่สตรีมไม่รองรับ Iteratorจะถูกแปลงกลับไปยังสตรีมโดยใช้บิตของ Java 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เป็นการยุติการดำเนินการประเภทพิเศษบนสตรีมและไม่ทำให้สตรีมทั้งหมดทำงานและเข้ามาในหน่วยความจำ! ขอบคุณพวก Java 8 สำหรับการออกแบบที่ยอดเยี่ยม!


และเป็นเรื่องดีมากที่คุณจะทำซ้ำในแต่ละชุดอย่างเต็มที่เมื่อรวบรวมและคงอยู่ที่ a List- คุณไม่สามารถเลื่อนการทำซ้ำองค์ประกอบภายในชุดงานได้เนื่องจากผู้บริโภคอาจต้องการข้ามทั้งชุดและหากคุณไม่ได้ใช้ องค์ประกอบเหล่านี้จะไม่ข้ามไปไกลมาก (ฉันได้ใช้สิ่งเหล่านี้ใน C # แม้ว่ามันจะง่ายกว่ามาก)
— ErikE

10

คุณยังสามารถใช้RxJava :

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

หรือ

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

หรือ

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

8

นอกจากนี้คุณยังสามารถดูcyclops-reactฉันเป็นผู้เขียนห้องสมุดนี้ มันใช้อินเทอร์เฟซjOOλ (และโดยส่วนขยาย JDK 8 Streams) แต่แตกต่างจาก JDK 8 Parallel Streams ตรงที่เน้นการทำงานแบบอะซิงโครนัส (เช่นอาจบล็อกการเรียก Async I / O) JDK Parallel Streams ในทางตรงกันข้ามมุ่งเน้นไปที่ความขนานของข้อมูลสำหรับการดำเนินการที่เชื่อมต่อกับ CPU ทำงานโดยการจัดการการรวมของงานตามอนาคตภายใต้ประทุน แต่นำเสนอ Stream API แบบขยายมาตรฐานให้กับผู้ใช้ปลายทาง

โค้ดตัวอย่างนี้อาจช่วยคุณในการเริ่มต้น

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

มีการสอนเกี่ยวกับแบทช์ที่นี่

และบทแนะนำทั่วไปเพิ่มเติมที่นี่

หากต้องการใช้ Thread Pool ของคุณเอง (ซึ่งน่าจะเหมาะสมกว่าสำหรับการบล็อก I / O) คุณสามารถเริ่มประมวลผลด้วยไฟล์

     LazyReact reactor = new LazyReact(40);

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

3

ตัวอย่าง Pure Java 8 ที่ใช้งานได้กับสตรีมคู่ขนานเช่นกัน

วิธีใช้:

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

วิธีการประกาศและการนำไปใช้:

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


1

ตัวอย่างง่ายๆโดยใช้ 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=====================");               
        }           
    }       
}

คำตอบของ Bruce นั้นครอบคลุมมากกว่า แต่ฉันกำลังมองหาสิ่งที่รวดเร็วและสกปรกในการประมวลผลไฟล์จำนวนมาก


1

นี่เป็นโซลูชัน java แท้ที่ประเมินอย่างเฉื่อยชา

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

คุณสามารถใช้ apache.commons:

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

ส่วนการแบ่งพาร์ติชันทำได้อย่างไม่เกียจคร้าน แต่หลังจากแบ่งรายการแล้วคุณจะได้รับประโยชน์จากการทำงานกับสตรีม (เช่นใช้สตรีมคู่ขนานเพิ่มตัวกรอง ฯลฯ ) คำตอบอื่น ๆ แนะนำวิธีแก้ปัญหาที่ซับซ้อนมากขึ้น แต่บางครั้งความสามารถในการอ่านและการบำรุงรักษาก็สำคัญกว่า (และบางครั้งก็ไม่ได้ :-))


ไม่แน่ใจว่าใครลงคะแนน แต่จะดีที่เข้าใจว่าทำไม .. ฉันให้คำตอบที่เสริมคำตอบอื่น ๆ สำหรับคนที่ไม่สามารถใช้ Guava
— Tal Joffe

คุณกำลังประมวลผลรายการที่นี่ไม่ใช่สตรีม
— Drakemor

@Drakemor ฉันกำลังประมวลผลรายการย่อย สังเกตการเรียกใช้ฟังก์ชัน stream ()
— Tal Joffe

แต่ก่อนอื่นให้เปลี่ยนเป็นรายการย่อยซึ่งจะทำงานไม่ถูกต้องสำหรับข้อมูลที่สตรีมจริง นี่คือการอ้างอิงถึงพาร์ติชัน: commons.apache.org/proper/commons-collections/apidocs/org/…
— Drakemor

1
TBH ฉันไม่ได้รับการโต้แย้งของคุณอย่างเต็มที่ แต่ฉันคิดว่าเราสามารถตกลงที่จะไม่เห็นด้วย ฉันได้แก้ไขคำตอบเพื่อให้สอดคล้องกับการสนทนาของเราที่นี่ ขอบคุณสำหรับการสนทนา
— Tal Joffe

1

สามารถทำได้อย่างง่ายดายโดยใช้ Reactor :

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

0

ด้วยJava 8และcom.google.common.collect.Listsคุณสามารถทำสิ่งต่างๆเช่น:

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

ในที่Tนี้คือประเภทของรายการในรายการอินพุตและUประเภทของรายการในรายการผลลัพธ์

และคุณสามารถใช้งานได้ดังนี้:

List<String> userKeys = [... list of user keys]
List<Users> users = BatchProcessingUtil.process(
    userKeys,
    10, // Batch Size
    partialKeys -> service.getUsers(partialKeys)
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.