web-dev-qa-db-ja.com

Java InputStreamで8ストリームを使用するにはどうすればよいですか?

Java.util.streams.StreamInputStreamにラップして、一度に1バイトまたは1文字を処理します。これを行う簡単な方法は見つかりませんでした。

次の演習を考えてみましょう。各文字がテキストファイルに出現する回数をカウントします。これを配列に格納して、tally[0]がaがファイルに出現する回数を格納したり、tally[1]がbが出現した回数を格納したりすることができます。ファイルを直接ストリーミングする方法が見つからなかったので、次のようにしました。

 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);

linesメソッドを使用せずにこれを達成する方法はありますか?テキストファイルの各行に文字列を作成する代わりに、各文字をストリームまたはストリームとして直接処理できますか?.

Java.io.InputStreamJava.util.Stream.streamにさらに直接変換できますか?

17
Thorn

最初に、タスクを再定義する必要があります。あなたは文字を読んでいるので、InputStreamではなくReaderStreamに変換したくありません。

発生する文字セット変換を再実装することはできません。 InputStreamReaderでは、Streambytesと結果のInputStreamsの間にn:mのマッピングが存在する可能性があるため、char操作を使用します。 。

Readerからストリームを作成するのは少し難しいです。アイテムと終了条件を取得するためのメソッドを指定するには、イテレータが必要です。

_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;
    }
};
_

イテレータを作成したら、スプリッタの迂回を使用してストリームを作成し、目的の操作を実行できます。

_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]++);
_

イテレータはよく知られていますが、新しいSpliteratorインターフェースを実装すると、任意の順序で呼び出すことができる2つのメソッド間で状態を維持する必要がないため、操作が直接簡素化されます。代わりに、read()呼び出しに直接マップできるtryAdvanceメソッドが1つだけあります。

_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:
…
_

ただし、気が変わって_Files.lines_を使用しても構わない場合は、より簡単に生活できることに注意してください。

_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