Vamos dizer que tenho enorme arquivo de log do servidor web que não se encaixa na memória. Eu preciso para transmitir esse arquivo para um método mapreduce e salvar a base de dados. Eu faço isso usando Java 8 fluxo de api. Por exemplo, posso obter uma lista após o processo de mapreduce tais como, o consumo por cliente, o consumo por ip, o consumo por conteúdo. Mas, minhas necessidades não são como aquele dado no meu exemplo. Já que não posso compartilhar código, eu só quero dar exemplo básico.
Por Java 8 Fluxo Api, eu quero ler o arquivo exatamente uma vez, obter 3 listas ao mesmo tempo, enquanto eu estou streaming de arquivos, paralelo ou sequencial. Mas paralela seria bom. Existe alguma maneira de fazer isso?
I se adaptaram a resposta a esta pergunta ao seu caso. O Spliterator costume vai "dividir" o fluxo em múltiplos fluxos que recolhem por propriedades diferentes:
@SafeVarargs
public static <T> long streamForked(Stream<T> source, Consumer<Stream<T>>... consumers)
{
return StreamSupport.stream(new ForkingSpliterator<>(source, consumers), false).count();
}
public static class ForkingSpliterator<T>
extends AbstractSpliterator<T>
{
private Spliterator<T> sourceSpliterator;
private List<BlockingQueue<T>> queues = new ArrayList<>();
private boolean sourceDone;
@SafeVarargs
private ForkingSpliterator(Stream<T> source, Consumer<Stream<T>>... consumers)
{
super(Long.MAX_VALUE, 0);
sourceSpliterator = source.spliterator();
for (Consumer<Stream<T>> fork : consumers)
{
LinkedBlockingQueue<T> queue = new LinkedBlockingQueue<>();
queues.add(queue);
new Thread(() -> fork.accept(StreamSupport.stream(new ForkedConsumer(queue), false))).start();
}
}
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
sourceDone = !sourceSpliterator.tryAdvance(t -> queues.forEach(queue -> queue.offer(t)));
return !sourceDone;
}
private class ForkedConsumer
extends AbstractSpliterator<T>
{
private BlockingQueue<T> queue;
private ForkedConsumer(BlockingQueue<T> queue)
{
super(Long.MAX_VALUE, 0);
this.queue = queue;
}
@Override
public boolean tryAdvance(Consumer<? super T> action)
{
while (queue.peek() == null)
{
if (sourceDone)
{
// element is null, and there won't be no more, so "terminate" this sub stream
return false;
}
}
// push to consumer pipeline
action.accept(queue.poll());
return true;
}
}
}
Você pode usá-lo da seguinte forma:
streamForked(Stream.of(new Row("content1", "client1", "location1", 1),
new Row("content2", "client1", "location1", 2),
new Row("content1", "client1", "location2", 3),
new Row("content2", "client2", "location2", 4),
new Row("content1", "client2", "location2", 5)),
rows -> System.out.println(rows.collect(Collectors.groupingBy(Row::getClient,
Collectors.groupingBy(Row::getContent,
Collectors.summingInt(Row::getConsumption))))),
rows -> System.out.println(rows.collect(Collectors.groupingBy(Row::getClient,
Collectors.groupingBy(Row::getLocation,
Collectors.summingInt(Row::getConsumption))))),
rows -> System.out.println(rows.collect(Collectors.groupingBy(Row::getContent,
Collectors.groupingBy(Row::getLocation,
Collectors.summingInt(Row::getConsumption))))));
// Output
// {client2={location2=9}, client1={location1=3, location2=3}}
// {client2={content2=4, content1=5}, client1={content2=2, content1=4}}
// {content2={location1=2, location2=4}, content1={location1=1, location2=8}}
Note que você pode fazer praticamente qualquer coisa que quiser com seus as cópias do fluxo. Como por seu exemplo, eu usei um empilhados groupingBy
coletor para agrupar as linhas por duas propriedades e, em seguida, resumiu a propriedade int. Assim, o resultado será um Map<String, Map<String, Integer>>
. Mas você também pode usá-lo para outros cenários:
rows -> System.out.println(rows.count())
rows -> rows.forEach(row -> System.out.println(row))
rows -> System.out.println(rows.anyMatch(row -> row.getConsumption() > 3))