สตรีม 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.