2016-02-05 12 views
6

Mi chiedevo quando ho creato il mio flusso infinito con Stream.generate come i flussi che sono nella libreria standard di arresto ...Come si fermano i flussi?

Per esempio, quando si dispone di un elenco con i record:

List<Record> records = getListWithRecords(); 
records.stream().forEach(/* do something */); 

Il flusso non sarà infinito e funzionerà per sempre, ma si fermerà quando tutti gli oggetti nella lista saranno attraversati. Ma come funziona? La stessa funzionalità si applica allo stream creato da Files.lines(path) (fonte: http://www.mkyong.com/java8/java-8-stream-read-a-file-line-by-line/).

E una seconda domanda, come si può interrompere uno stream creato con Stream.generate nello stesso modo?

risposta

9

I flussi limitati semplicemente non vengono creati tramite Stream.generate.

Il modo standard di implementazione di uno stream consiste nell'implementare uno Spliterator, talvolta utilizzando the Iterator detour. In entrambi i casi, l'implementazione ha un modo per segnalare una fine, ad es. quando Spliterator.tryAdvance restituisce false o il suo metodo forEachRemaining restituisce o, in caso di origine Iterator, quando hasNext() restituisce false.

A Spliterator può anche riportare il numero previsto di elementi prima dell'inizio dell'elaborazione.

Streams, create tramite uno dei metodi di fabbrica all'interno dell'interfaccia Stream, come Stream.generate possono essere eseguite sia, da un Spliterator come bene o utilizzando le funzionalità interne di attuazione torrente, ma a prescindere da come loro applicazione, si don' t mettere le mani su questa implementazione per modificare il loro comportamento, quindi l'unico modo per rendere finito un flusso di questo tipo consiste nel concatenare un'operazione allo limit allo stream.

Se si desidera creare un flusso finito non vuoto che non sia supportato da una matrice o da una raccolta e nessuna delle sorgenti di flusso esistenti si adatti, è necessario implementare il proprio Spliterator e create a stream out of it. Come detto sopra, è possibile utilizzare un metodo esistente per creare un Spliterator su un Iterator, ma si dovrebbe resistere alla tentazione di utilizzare un Iterator solo perché è familiare. Un Spliterator non è difficile da implementare:

/** like {@code Stream.generate}, but with an intrinsic limit */ 
static <T> Stream<T> generate(Supplier<T> s, long count) { 
    return StreamSupport.stream(
       new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) { 
     long remaining=count; 

     public boolean tryAdvance(Consumer<? super T> action) { 
      if(remaining<=0) return false; 
      remaining--; 
      action.accept(s.get()); 
      return true; 
     } 
    }, false); 
} 

Da questo punto di partenza, si può aggiungere sostituzioni per i default metodi Spliterator interfaccia, costi di sviluppo di pesi e potenziali miglioramenti delle prestazioni, ad esempio

static <T> Stream<T> generate(Supplier<T> s, long count) { 
    return StreamSupport.stream(
       new Spliterators.AbstractSpliterator<T>(count, Spliterator.SIZED) { 
     long remaining=count; 

     public boolean tryAdvance(Consumer<? super T> action) { 
      if(remaining<=0) return false; 
      remaining--; 
      action.accept(s.get()); 
      return true; 
     } 

     /** May improve the performance of most non-short-circuiting operations */ 
     @Override 
     public void forEachRemaining(Consumer<? super T> action) { 
      long toGo=remaining; 
      remaining=0; 
      for(; toGo>0; toGo--) action.accept(s.get()); 
     } 
    }, false); 
} 
+1

Perché evitare di utilizzare un iteratore per definire la spliterator? Ho appena visto che BufferedReader.lines() usa questo approccio per creare il suo flusso finito, per esempio. – Juru

+5

'BufferedReader.lines()' è un buon esempio. Guarda l'implementazione di 'next()' e ['hasNext()'] (http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/io /BufferedReader.java?av = f # 566) e come devono mantenere lo stato tra le invocazioni. Al contrario, uno spliterator è straight-forward, è necessario un singolo metodo: 'tryAdvance (Consumer c) {String line = readLine(); if (line == null) restituisce false; c.accept (line); ritorna vero; } 'e questo è tutto. Più semplice da implementare (aggiungi la gestione delle eccezioni e ancora metà della dimensione del codice) e nessun wrapper necessario ... – Holger

+0

Questa implementazione è sicura per i thread? Ha bisogno di essere? – WillD

0

ho creato una soluzione generica per questo

public class GuardedSpliterator<T> implements Spliterator<T> { 

    final Supplier<? extends T> generator; 

    final Predicate<T> termination; 

    final boolean inclusive; 

    public GuardedSpliterator(Supplier<? extends T> generator, Predicate<T> termination, boolean inclusive) { 
    this.generator = generator; 
    this.termination = termination; 
    this.inclusive = inclusive; 
    } 

    @Override 
    public boolean tryAdvance(Consumer<? super T> action) { 
    T next = generator.get(); 
    boolean end = termination.test(next); 
    if (inclusive || !end) { 
     action.accept(next); 
    } 
    return !end; 
    } 

    @Override 
    public Spliterator<T> trySplit() { 
    throw new UnsupportedOperationException("Not supported yet."); 
    } 

    @Override 
    public long estimateSize() { 
    throw new UnsupportedOperationException("Not supported yet."); 
    } 

    @Override 
    public int characteristics() { 
    return Spliterator.ORDERED; 
    } 

} 

utilizzo è piuttosto semplice:

GuardedSpliterator<Integer> source = new GuardedSpliterator<>(
    () -> rnd.nextInt(), 
    (i) -> i > 10, 
    true 
); 

Stream<Integer> ints = StreamSupport.stream(source, false); 

ints.forEach(i -> System.out.println(i));  
Problemi correlati