web-dev-qa-db-fra.com

Comment puis-je utiliser Java 8 flux avec un InputStream?

Je voudrais envelopper un Java.util.streams.Stream autour d'un InputStream pour traiter un octet ou un caractère à la fois. Je n'ai trouvé aucun moyen simple de le faire.

Considérez l'exercice suivant: Nous souhaitons compter le nombre de fois que chaque lettre apparaît dans un fichier texte. Nous pouvons stocker cela dans un tableau afin que tally[0] stockera le nombre de fois où apparaît dans le fichier, tally[1] enregistre le nombre de fois où b apparaît et ainsi de suite. Comme je n'ai pas trouvé de moyen de diffuser le fichier directement, j'ai fait ceci:

 int[] tally = new int[26];
 Stream<String> lines = Files.lines(Path.get(aFile)).map(s -> s.toLowerCase());
 Consumer<String> charCount = new Consumer<String>() {
   public void accept(String t) {
      for(int i=0; i<t.length(); i++)
         if(Character.isLetter(t.charAt(i) )
            tall[t.charAt(i) - 'a' ]++;
   }
 };
 lines.forEach(charCount);

Existe-t-il un moyen d'y parvenir sans utiliser la méthode lines? Puis-je simplement traiter chaque caractère directement en tant que flux ou flux au lieu de créer des chaînes pour chaque ligne du fichier texte.

Puis-je convertir plus directement Java.io.InputStream en Java.util.Stream.stream?

17
Thorn

Tout d'abord, vous devez redéfinir votre tâche. Vous lisez caractères, donc vous ne voulez pas convertir un InputStream mais un Reader en Stream.

Vous ne pouvez pas réimplémenter la conversion de jeu de caractères qui se produit, par exemple dans un InputStreamReader, avec des opérations Stream car il peut y avoir des mappages n: m entre les bytes des InputStream et les chars résultants .

La création d'un flux à partir d'un Reader est un peu délicate. Vous aurez besoin d'un itérateur pour spécifier une méthode pour obtenir un article et une condition de fin:

PrimitiveIterator.OfInt it=new PrimitiveIterator.OfInt() {
    int last=-2;
    public int nextInt() {
      if(last==-2 && !hasNext())
          throw new NoSuchElementException();
      try { return last; } finally { last=-2; }
    }
    public boolean hasNext() {
      if(last==-2)
        try { last=reader.read(); }
        catch(IOException ex) { throw new UncheckedIOException(ex); }
      return last>=0;
    }
};

Une fois que vous avez l'itérateur, vous pouvez créer un flux en utilisant le détour d'un séparateur et effectuer l'opération souhaitée:

int[] tally = new int[26];
StreamSupport.intStream(Spliterators.spliteratorUnknownSize(
  it, Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL), false)
// now you have your stream and you can operate on it:
  .map(Character::toLowerCase)
  .filter(c -> c>='a'&&c<='z')
  .map(c -> c-'a')
  .forEach(i -> tally[i]++);

Notez que bien que les itérateurs soient plus familiers, l'implémentation de la nouvelle interface Spliterator simplifie directement l'opération car elle ne nécessite pas de maintenir l'état entre deux méthodes qui pourraient être appelées dans un ordre arbitraire. Au lieu de cela, nous n'avons qu'une seule méthode tryAdvance qui peut être mappée directement à un appel read():

Spliterator.OfInt sp = new Spliterators.AbstractIntSpliterator(1000L,
    Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.NONNULL) {
        public boolean tryAdvance(IntConsumer action) {
            int ch;
            try { ch=reader.read(); }
            catch(IOException ex) { throw new UncheckedIOException(ex); }
            if(ch<0) return false;
            action.accept(ch);
            return true;
        }
    };
StreamSupport.intStream(sp, false)
// now you have your stream and you can operate on it:
…

Cependant, notez que si vous changez d'avis et souhaitez utiliser Files.lines, Vous pouvez avoir une vie beaucoup plus facile:

int[] tally = new int[26];
Files.lines(Paths.get(file))
  .flatMapToInt(CharSequence::chars)
  .map(Character::toLowerCase)
  .filter(c -> c>='a'&&c<='z')
  .map(c -> c-'a')
  .forEach(i -> tally[i]++);
20
Holger