Vor einiger Zeit musste ich größere Listen von Daten abarbeiten und die Ergebnisse zwischenspeichern. An mehreren Stellen im Code hatte ich dann Batch-Schleifen, die eine Liste in Batches á N Stück aufteilten und diese verarbeiteten. Die Gelegenheit, das etwas generischer zu Formulieren.
Die Grundstruktur einer batchweisen Verarbeitung einer Liste von Elementen sieht wie folgt aus:
List data = ... final int batchSize = 50; int fromOffset = 0; do { int toOffset = Math.min(data.size(), fromOffset + batchSize); List batch = data.subList(ftomOffset, toOffset); // verarbeite Batch fromOffset = toOffset; } while (fromOffset < data.size()); |
Formuliert man das ganze mit einem Context-Objekt, in dem Informationen zum aktuellen Batch stehen, sowie ein Interface für Konsumenten, erhält man eine generische Batch-Abarbeitung:
@Log4j public class Batch { public interface Consumer<T> { void consume(List<T> batch, Context context); } @Getter @AllArgsConstructor @Builder @EqualsAndHashCode public static class Context { private int currentBlockSize; private int blockSize; private int currentOffset; } public static <T> void process(List<T> data, int blockSize, Consumer<T> consumer) { final int maxOffset = data.size(); int fromOffset = 0; do { int currentBlockSize = Math.min(blockSize, maxOffset - fromOffset); int toOffset = fromOffset + currentBlockSize; log.info("Verarbeite Block {} bis {} von {}", fromOffset, toOffset, maxOffset); consumer.consume(data.subList(fromOffset, toOffset), Context.builder() .currentOffset(fromOffset) .currentBlockSize(currentBlockSize) .blockSize(blockSize) .build()); fromOffset = toOffset; } while (fromOffset < maxOffset); } } |
Die Nutzung ist relativ einfach:
@Test public void process() { List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> receivedBatchEntries = new ArrayList<>(); List<Batch.Context> contexts = new ArrayList<>(); Batch.process(ints, 3, (batch, context) -> { receivedBatchEntries.addAll(batch); contexts.add(context); }); assertThat(receivedBatchEntries, contains(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); assertThat(contexts, contains( Batch.Context.builder().blockSize(3).currentBlockSize(3).currentOffset(0).build(), Batch.Context.builder().blockSize(3).currentBlockSize(3).currentOffset(3).build(), Batch.Context.builder().blockSize(3).currentBlockSize(3).currentOffset(6).build(), // Letzter lauf hat nur ein Element Batch.Context.builder().blockSize(3).currentBlockSize(1).currentOffset(9).build() )); } |
Das Ganze kann man dann auch auf asynchrone und parallele Abarbeitungen erweitern.