如何将 Java 8 Streams 与 InputStream 一起使用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23835174/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 01:29:58  来源:igfitidea点击:

How can I use Java 8 Streams with an InputStream?

javaiojava-8java-stream

提问by Thorn

I would like to wrap a java.util.streams.Streamaround an InputStreamto process one Byte or one Character at a time. I didn't find any simple way of doing this.

我想java.util.streams.Stream围绕 an 一次InputStream处理一个字节或一个字符。我没有找到任何简单的方法来做到这一点。

Consider the following exercise: We wish to count the number of times each letter appears in a text file. We can store this in an array so that tally[0]will store the number of times a appears in the file, tally[1]stores the number of time b appears and so on. Since I couldn't find a way of streaming the file directly, I did this:

考虑以下练习:我们希望计算每个字母在文本文件中出现的次数。我们可以将其存储在一个数组中,以便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);

Is there a way of accomplishing this without using the linesmethod? Can I just process each character directly as a Stream or Stream instead of creating Strings for each line in the text file.

有没有办法在不使用该lines方法的情况下完成此操作?我可以直接将每个字符作为 Stream 或 Stream 处理,而不是为文本文件中的每一行创建字符串。

Can I more direcly convert java.io.InputStreaminto java.util.Stream.stream?

我可以更直接地转换java.io.InputStreamjava.util.Stream.stream吗?

采纳答案by Holger

First, you have to redefine your task. You are reading characters, hence you do not want to convert an InputStreambut a Readerinto a Stream.

首先,您必须重新定义您的任务。您正在阅读字符,因此您不想将 an InputStreambut aReader转换为 a Stream

You can't re-implement the charset conversion that happens, e.g. in an InputStreamReader, with Streamoperations as there can be n:m mappings between the bytes of the InputStreamand the resulting chars.

您不能使用操作重新实现发生的字符集转换,例如在 an 中InputStreamReaderStream因为bytes 的 sInputStream和结果chars之间可能存在 n:m 映射。

Creating a stream out of a Readeris a bit tricky. You will need an iterator to specify a method for getting an item and an end condition:

从 a 创建流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;
    }
};

Once you have the iterator you can create a stream using the detour of a spliterator and perform your desired operation:

一旦你有了迭代器,你就可以使用拆分器的绕道创建一个流并执行你想要的操作:

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


Note that while iterators are more familiar, implementing the new Spliteratorinterface directly simplifies the operation as it doesn't require to maintain state between two methods that could be called in arbitrary order. Instead, we have just one tryAdvancemethod which can be mapped directly to a read()call:

请注意,虽然迭代器更熟悉,但实现新Spliterator接口直接简化了操作,因为它不需要维护可以以任意顺序调用的两个方法之间的状态。相反,我们只有一种tryAdvance可以直接映射到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:
…


However, note that if you change your mind and are willing to use Files.linesyou can have a much easier life:

但是,请注意,如果您改变主意并愿意使用,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]++);